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.

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.

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ł.

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ł.

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 z wieloma miejscami po przecinku, 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.

4 uwagi do wpisu “21,60-15,00=6,61

  1. Pingback: Podsumowanie roku 2019 – Bugfree blog

  2. VanB

    Zgodnie z wytycznymi ministerstwa finansów (popartymi kilkoma procesami z tymi bardziej opornymi) rabat MUSI być rozłożony proporcjonalnie i to w kwocie netto na wszystkie pozycje na paragonie. Chodzi o to aby nie rabatować tylko pozycji opodatkowanej najwyższą stawką podatku, bo budżet państwa jest wtedy w plecy najbardziej – a rabat jest mniej obciążający dla firmy – taka magia procentów 🙂

    Więc jak te Twoje 15 pln rozłożymy na wszystkie stawki podatku proporcjonalnie, zaokrąglimy do pełnych groszy, naliczymy Vat, zaokrąglimy do pełnych groszy…. To sorry to się nie uda aby zawsze dostać 15 pln rabatu w kwocie brutto. Oczywiście dobry program powinien tak to zrobić aby dać 15,00 lub 15,01 rabatu jak się nie da inaczej. Tak aby klient zawsze był zadowolony. I tak to powinno zostać wytestowane i poprawione.

    Miłego

    Polubione przez 1 osoba

  3. Wyższa półka. Ja poległem na tłumaczeniu mojej kierowniczce, dlaczego suma kolumny za pomocą kalkulatora nie równa się wynikowi funkcji sumy w arkuszu Excel. Co przechowuje komórka, a co prezentuje jej format to dla mnie nauczka 😁

    Polubienie

Dodaj komentarz