float'ами. Берётся список чисел:>>> mylist = [И складывается, начиная сначала с одного конца, а затем с другого:
... 614.3,
... 453.44,
... 220.8,
... 514.32,
... 322.32,
... 330.3,
... 230.3,
... 518.32,
... 419.52]
>>> sum(mylist) == sum(mylist[::-1])И суммы не сходятся.
False
Тема уже избита до невозможности, и все вроде бы уже смирились, что арифметика над числами с плавающей точкой категорически сломана (ладно, она не сломана, она просто с погрешностями). В интернете особенно часто за эту особенность почему-то достаётся JavaScript'у, но на самом деле этим болеют все языки программирования, которые реализуют числа по стандарту IEEE 754, то есть практически все. Даже страничка с забавным доменом есть на эту тему: 0.30000000000000004.com.
Оказывается, что сложение чисел с плавающей точкой не реализует одно из математических свойств сложения — ассоциативность. То есть
a + (b + c) не всегда равно (a + b) + c. То есть в зависимости от порядка, в котором складывать числа, погрешность может накапливаться по-разному. Для меня стало открытием, что арифметика сломана настолько.Минимальный пример:
>>> ε = 1e-16 # small number
>>> print(left := (1 + ε) + ε)
1.0
>>> print(right := 1 + (ε + ε))
1.0000000000000002
>>> left == right
False
Есть специальные алгоритмы, которые позволяют избегать погрешностей при сложении чисел с плавающей точкой, и один из них реализован в стандартной библиотеке как math.fsum:>>> import mathИ раз уж мы затронули тему чисел с плавающей точкой, то пара хороших советов, которые помогут избежать проблем:
>>> math.fsum(mylist) == math.fsum(mylist[::-1])
True
🍁 Если погрешность для вас неприемлема, то конвертируйте ваши числа в
decimal.Decimal — да, будет значительно медленнее, но зато точно:>>> 0.1 + 0.2🍁 А если вы считаете деньги, то просто выражайте суммы в целых числах (центы и копейки вместо долларов и рублей) — так будет намного проще и надёжнее. Не используйте
0.30000000000000004
>>> from decimal import Decimal
>>> Decimal("0.1") + Decimal("0.2")
Decimal('0.3')
float для денежных расчётов! Если вы думаете, что погрешности у float начинаются только где-то в пятнадцатом знаке после запятой, то у меня для вас плохие новости — чем больше числа, тем больше и погрешности. Цена даже одной ошибки может быть очень высока 😬