Typy zmiennoprzecinkowe a obliczenia finansowe

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.