21,60-15,00=6,61

W tym artykule poruszam temat testowania poprawności obliczeń. Wyjaśnię też w jakich okolicznościach doszło do tego, że 21,60-15,00=6,61.

W pracy Testera spotykamy się z różnymi zadaniami. Sprawdzamy na przykład czy:

  • aplikacja jest zgodna z zaprojektowanymi mockupami,
  • dane się poprawnie zapisują, wyświetlają,
  • aplikacja działa wystarczająco szybko,
  • spełnia standardy bezpieczeństwa,
  • aplikacja poprawnie integruje się z innymi systemami,
  • i inne.

W niektórych systemach kluczowe jednak mogą być operacje na liczbach, wszelkie wyliczenia, przekształcenia, itp. Często różnica w wyliczeniach na poziomie jednego grosza, czy centa może nie być akceptowalna. Duże znaczenie może mieć precyzja używanych danych oraz ich zaokrąglanie w trakcie obliczeń. Błędy związane z zaokrągleniami, to ciekawe przypadki i podstępne skurczybyki. Kto kiedykolwiek testował raporty, wyliczenia algorytmów, to pewnie wie o czym będzie ten tekst.

Wstęp do przypadku

Kilka dni temu, jak co sobotę, robiłem zakupy w piekarni. Celowo nie podaję jej nazwy, bo nie w tym rzecz. Choć faktem jest, że jest to wspaniałe miejsce z prawdziwym chlebem i genialnymi wyrobami cukierniczymi. Dla jasności dodam, że tekst nie jest zarzutem czy skargą na piekarnię, a raczej próbą rozwiązania pewnej zagadki, która mnie zaintrygowała.

Otóż w tym miejscu, jak w wielu innych jest pewien program lojalnościowy. Polega on na tym, że po wydaniu pewnej kwoty pieniędzy pozwala na uzyskanie rabatu na kolejne zakupy. Rabat wynosi 15 zł. To jest kluczowe, że rabat jest na konkretną kwotę, a nie procent.

Co w tym niezwykłego?

No niezwykły jest sposób naliczania rabatu. Okazuje się, że nie jest to zwykłe odjęcie 15 złotych od wartości zakupów. Z jakiegoś powodu te 15 złotych jest rozkładane proporcjonalnie na wszystkie pozycje na paragonie, co powoduje odpowiednie zmniejszenie ceny każdego produktu. I tak po naliczeniu rabatu coś co kosztuje 4 złote, na paragonie jest widoczne z ceną 1,22 zł.

Wróćmy jednak do przypadku z paragonu przedstawionego powyżej. Kupiłem cztery produkty:

  • pierwszy: 0,5*14,00 zł = 7 zł
  • drugi: 3*1,20 zł = 3,60 zł
  • trzeci: 2*1,50 zł = 3,00 zł
  • czwarty: 2*4,00 zł = 8,00 zł
  • w sumie: 21,60 zł

zapłaciłem za całość 6,61 zł po uwzględnieniu 15 zł rabatu. Jak to możliwe? To jest miejsce, które każdego Testera powinno zainteresować. Czy nie wygląda to na błąd?

21,60 – 15,00 = 6,61, serio?

W moim przypadku, podanym powyżej, rabat sumował się do 14,99 zł zamiast 15,00 zł. Zaczęło mnie nurtować z czego to może wynikać, bo wygląda to na błąd w obliczeniach. Prawdopodobnie spowodowany zaokrągleniami i nieobsłużeniem pewnych szczególnych przypadków. Błąd z pewnością nie występuje zawsze, a zależy od danych, czyli cen i ilości poszczególnych produktów.

Jak to jest liczone?

Małe kminionko jak to mogło zostać policzone. Przypominam: pierwotnie powinienem zapłacić 21,60 zł, jak to widać w tabeli poniżej.

Brak alternatywnego tekstu dla tego zdjęcia

Teraz wyliczam procentowy rabat (X) jaki został uwzględniony. W tym celu posłużyłem się proporcją widoczną poniżej.

proporcja:
21,6 - 100%
21,6-15 - X

X = (21,6-15)/21,6*100% = 30,5555555%

Teraz rozpatrzmy dwa warianty wyliczeń.

  • wariant1: najpierw liczymy cenę pojedynczej sztuki produktu po rabacie, a następnie zaokrąglimy ją do dwóch miejsc po przecinku. Dopiero później liczymy wartość danej pozycji posługując się już nową ceną.
nowa_cena = zaokr(cena*X;2)
nowa_wartość = nowa_cena * ilość
  • wariant2: od razu liczymy wartość, a później zaokrąglimy ją do dwóch miejsc po przecinku
nowa_wartość = zaokr(cena*X*ilość;2)

Wyniki znaleźć można w tabeli poniżej. Widać, że różnica pojawia się dla drugiego produktu. Z paragonu jednoznacznie wynika, który wariant został zastosowany. Cena na paragonie jest równa 0,37, czyli wywnioskować można, że rabat jest liczony dla pojedynczego produktu.

Brak alternatywnego tekstu dla tego zdjęcia

Czy to szczególny przypadek?

Oczywiście ten błąd nie pojawia się zawsze. W większości przypadków klienci dostają zapewne pełny rabat. To jest moje przypuszczenie, bo przytrafiło mi się to pierwszy raz. Być może ma to związek z niedawną zmianą cen, które mogą bardziej sprzyjać ujawnieniu się tego buga. Tak jak pisałem wyżej, występuje on tylko dla konkretnych kombinacji danych wejściowych. Co ciekawe, problem prawdopodobnie nie byłby widoczny, gdybym wziął cztery sztuki tego drugiego produktu. Piszę prawdopodobnie, ze względu na brak wiedzy o sposobie implementacji algorytmu. Mogły by się więc pojawić inne rozbieżności.

Zróbmy zatem szybkie wyliczenie dla przypadku ze zwiększoną ilością drugiego produktu. Mamy zatem tabelkę jak poniżej. Całkowita kwota, w tym przypadku wynosi 22,80 zł. Co oznacza, że po odjęciu rabatu, do zapłaty zostaje 7,80 zł.

Brak alternatywnego tekstu dla tego zdjęcia

Ponownie liczymy proporcję jak wcześniej.

proporcja:
22,8 - 100%
22,8-15 - X

X = (22,8-15)/22,8*100% = 34,210526%

Teraz przeliczmy wszystko w sposób opisany wcześniej jako wariant1. Tym razem rabat powinien zostać poprawnie naliczony, bo do zapłaty mamy 7,80 zł.

Brak alternatywnego tekstu dla tego zdjęcia

Podsumowanie

W przypadku operacji na liczbach, należy zwrócić uwagę na sposób liczenia i szukać ewentualnych problemów w zaokrągleniach. Mogą one mieć miejsce na różnych etapach obliczeń, co może utrudniać ich lokalizację. Zakładam jednak, że takie algorytmy obliczeniowe powinny być dobrze opisane w wymaganiach. Tak to przynajmniej wyglądało w mojej poprzedniej pracy. Dobrze jest też porozmawiać z developerem jak to zostało rzeczywiście zaimplementowane. Już wtedy można znaleźć pewne rozbieżności. Zdarza się też tak, że niewielkie różnice w wyliczeniach są akceptowane dla użytkowników danej aplikacji. Taką informację też powinniśmy mieć w wymaganiach lub uzyskać ją od klienta.

Dobór odpowiednich danych testowych jest zwykle kluczowy w tego typu testach. Często jest tak, że błędne wyliczenia pojawiają się tylko w specyficznych przypadkach. Nie wystarczy sprawdzić algorytmu dla danych wejściowych będących liczbami całkowitymi, a raczej szukać tych z większą precyzją. Weryfikację należy wykonać dla co najmniej kilku zestawów wartości wejściowych. Będzie to zależeć od stopnia skomplikowania zaimplementowanych operacji arytmetycznych.

Wróćmy jednak do tego konkretnego przypadku rozważanego w tekście. Według mnie jest to oczywisty błąd, który bym zaraportował. Rabat zawsze powinien wynosić 15 złotych, a nie być dziełem losowego doboru produktów. Inaczej mogłoby to działać, gdyby rabat był naliczany procentowo. Tutaj jednak dodatkowy warunek powinien zostać obsłużony, kiedy wyliczone ulgi dla poszczególnych produktów nie sumuje się do wysokości rabatu. Domyślam się, że może nie być łatwo uzyskać odpowiednią kwotę zniżki, bo w obliczeniach pojawiają się liczby niewymierne, które jakoś trzeba obsłużyć. Jednak tych kilka groszy różnicy powinno zostać odjęte z dowolnej ceny, by funkcjonalność naliczania rabatu działała poprawnie we wszystkich wypadkach.

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Wyloguj /  Zmień )

Zdjęcie na Google

Komentujesz korzystając z konta Google. Wyloguj /  Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Wyloguj /  Zmień )

Zdjęcie na Facebooku

Komentujesz korzystając z konta Facebook. Wyloguj /  Zmień )

Połączenie z %s