Zasada, że do obliczeń finansowych nie należy stosować liczb zmiennoprzecinkowych jest dość powszechnie chyba znana wśród programistów. Mniej znana już jest odpowiedź na pytanie dlaczego tak jest, dziś bowiem w szkołach lekcje z komputerami szumnie nazywają się informatyką, ale informatyki nikt na nich nie uczy, co najwyżej technologii informacyjnej. Oto więc zagadnienie z informatyki prawdziwej.
Przypuśćmy, że w sklepie internetowym mamy produkt, którego cena wynosi 12 zł 80 gr. Każdy księgowy, a także każdy matematyk zgodzi się, że jest to 12,80 zł, gdzie przecinek oznacza rozwinięcie dziesiętne ułamka. Na razie wszystko jest w najlepszym porządku. Musimy sobie jednak uświadomić, co stanie się z tą liczbą po wprowadzeniu do pamięci maszyny cyfrowej (dziś się mówi "komputera", ale ja jestem z tego pokolenia, co jeszcze pamięta "ODRĘ"). Jak wiemy, liczba w pamięci maszyny reprezentowana jest przez ciąg zer i jedynek, który odpowiada zapisowi tej liczby w systemie binarnym. Otóż część całkowita naszej ceny w systemie dwójkowym jest stosunkowo prosta do napisania i wynosi 1100.
Przy okazji przypomnijmy sobie algorytm tej zamiany:
12:2 = 6 reszta 0
6:2 = 3 reszta 0
3:2 = 1 reszta 1
1:2 = 0 reszta 1
Te reszty wzięte od końca dają nam właśnie postać binarną. A co z częścią ułamkową? Otóż algorytm zamiany polega na mnożeniu przez 2, z wyników wyłączamy część całkowitą a pozostałą część ułamkową mnożymy ponownie przez 2 (powtarzamy procedurę). Wyłączone części całkowite dają nam kolejne miejsca rozwinięcia binarnego. Stanie się to jasne na prostym przykładzie. Znajdźmy binarne rozwinięcie ułamka 0,375:
0,375 * 2 = 0,75 część całkowita 0
0,75 * 2 = 1,5 część całkowita 1
0,5 * 2 = 1 część całkowita 1
i koniec, bo nie pozostała nam już żadna część ułamkowa do dalszego mnożenia. A zatem rozwinięcie binarne liczby dziesiętnej 0,375 to 0,011.
Spróbujmy teraz zabrać się za część ułamkową naszej ceny.
0,8 * 2 = 1,6 (część całkowita wynosi 1)
0,6 * 2 = 1,2 (część całkowita wynosi 1)
0,2 * 2 = 0,4 (część całkowita wynosi 0)
0,4 * 2 = 0,8 (część całkowita wynosi 0)
0,8 * 2 = 1,6 (część całkowita wynosi 1)
...
No i już widać co będzie dalej! Rozwinięcie binarne naszego ułamka będzie okresowe, czyli nasza liczba 12,80 w reprezentacji dwójkowej będzie miała postać 1100,110011001100... Maszyna oczywiście zaokrągli ten ułamek. Konsekwencje tego są dość zaskakujące. Jako wynik prostego mnożenia liczb 0.8 * 1.1 w Pythonie otrzymamy np.:
ActivePython 2.7.0.2 (ActiveState Software Inc.) based on Python 2.7 (r27:82500, Aug 23 2010, 17:18:21) [MSC v.1500 32 bit (Intel)] Type "help", "copyright", "credits" or "license" for more information. >>> 0.8 * 1.1 0.8800000000000001 >>>
W obliczeniach finansowych tego typu maszynowe zaokrąglenia mogą nam zafałszować np. podatek VAT ;)
Ratunkiem (w Pythonie) jest klasa Decimal.
Moduł decimal zawiera bibliotekę, która potrafi wykonywać dokładne obliczenia na ułamkach dziesiętnych (skończonych) a takie występują w obliczeniach finansowych. W porzypadku jednak kiedy wykonamy dzielenie ułamków czy ogólniej liczb, powstaje pytanie o dokładność, jaką chcemy uzyskać. Otóż używając klasy Decimal możemy sobie tę dokładność ustawić:
>>> from decimal import * >>> getcontext().prec = 6 >>> Decimal('1')/Decimal('3') Decimal('0.333333')
Jeśli jednak na kolejnych miejscach znaczących będą zera - zostaną pominięte. Możemy teraz np wyliczyć sobie cenę netto zakładając, że nasze 12,80 było ceną brutto, a VAT wynosi 23%:
>>> netto = Decimal('12.80')/Decimal('1.23') >>> netto Decimal('10.40650406504065040650406504') >>> netto.quantize(Decimal('1.00')) Decimal('10.41')
Nie należy mylić atrybutu "prec" z metodą quantize. Prec wyznacza ilość cyfr znaczących niezależnie od tego, gdzie jest przecinek, a metodą quantize mozna określić ilość miejsc za przecinkiem.
Moduł decimal zawiera wiele innych użytecznych metod, z którymi warto się zapoznać tutaj: http://docs.python.org/library/decimal.html
W Django wraz z wersją 1.0 zostało wprowadzone pole DecimalField.