Коротко. float — это двоичная дробь по стандарту IEEE 754 (64 бита,
тип double в C). Числа вроде 0.1 в двоичной системе бесконечны, поэтому
хранятся с округлением, и ошибки накапливаются.
0.1 + 0.2 # 0.30000000000000004
0.1 + 0.2 == 0.3 # FalseЭто не баг Python — так ведёт себя любой язык с IEEE 754. Параметры конкретной
сборки можно посмотреть в sys.float_info.
Подвох. Сравнивать float на точное равенство нельзя. Правильно — с
допуском:
import math
math.isclose(0.1 + 0.2, 0.3) # Truemath.isclose умеет и относительный (rel_tol), и абсолютный (abs_tol)
допуск — второй нужен при сравнении с нулём.
Глубже. Отсюда же главное практическое правило: деньги не хранят во
float. Для них берут decimal.Decimal (десятичная арифметика с
настраиваемой точностью) или целое число копеек.
from decimal import Decimal
Decimal('0.1') * 3 # Decimal('0.3')
0.1 * 3 # 0.30000000000000004Для точных рациональных дробей есть fractions.Fraction.
Коротко. nan не равен ничему, включая самого себя. inf — корректное
значение, больше любого числа.
float('nan') == float('nan') # False
math.isnan(float('nan')) # True — единственный правильный способ проверки
float('inf') > 10 ** 100 # TrueПодвох. Из-за nan != nan сортировка списка с nan даёт неопределённый
результат, а x in [float('nan')] может внезапно вернуть True — потому что
in сначала проверяет идентичность (is), и только потом равенство.
Коротко. Python использует банковское округление (к ближайшему чётному) — это уменьшает систематическое смещение при массовых вычислениях.
round(0.5) # 0
round(1.5) # 2
round(2.5) # 2Подвох. Ожидание «.5 округляется вверх» — распространённая ошибка. Если
нужно именно оно, берут decimal.Decimal с явным режимом ROUND_HALF_UP.
Остальные методы — в документации.