Nie dalej jak wczoraj kolega podesłał mi łatkę do mojej biblioteki do słownego zapisu liczb i kwot. Nie zdradzę od kogo, by chronić niewinnego. Grunt, że łatka wyglądała tak:

--- to_words_pl.py	(upstream)
+++ to_words_pl.py	(working copy)
@@ -82,7 +82,7 @@
         iteration += 1
     if unit:
         result.append(unit)
-    result.append(u'%d/100' % int(remainder * 100))
+    result.append(u'%d/100' % int(round(remainder * 100)))
     result = ' '.join(result)
     return result

Zdziwiłem się bardzo, bo zwykłem kwoty odpowiednio zaokrąglać do dwóch miejsc po przecinku. Co się jednak okazało? 0.48 zamieniało się w 0.47. A dokładniej? W 0.47999999999999998. Tuś mi, ptaszku.

Patrząc na 0.48 tak naprawdę w głowie widziałem decimal.Decimal('0.48'). Jak się jednak okazuje, niektórzy próbują operacje finansowe przeprowadzać na liczbach zmiennopozycyjnych. Nie używamy typu float do operacji finansowych. Dlaczego?

>>> 0.48
0.47999999999999998
>>> 0.82
0.81999999999999995

Do operacji na liczbach o znanej precyzji używamy typu decimal.Decimal i jego kontrolowanego (i konfigurowalnego!) mechanizmu zaokrąglania:

>>> from decimal import Decimal
>>> Decimal('0.48') + Decimal('0.12')
Decimal('0.60')
>>> vat = Decimal('0.48') * Decimal('0.22')
>>> vat.quantize(Decimal('0.01'))
Decimal('0.11')