Чему равно выражение 0.1 + 0.2 == 0.3 в Python и почему
Что проверяют
Знаете ли вы, что float в Python это двоичное число двойной точности, и понимаете ли последствия. Вопрос простой, но по нему видно, разбирались ли вы в представлении чисел или просто запомнили, что «плавающая точка неточная».
Как отвечать
Ответ: False. Причина в том, что float соответствует двоичному числу двойной точности по стандарту IEEE 754, а десятичные 0.1, 0.2 и 0.3 в двоичной системе бесконечные периодические дроби. Каждое хранится как ближайшее представимое значение, и сумма двух округлённых чисел не совпадает с округлением точной суммы:
>>> 0.1 + 0.2
0.30000000000000004
>>> from decimal import Decimal
>>> Decimal(0.1)
Decimal('0.1000000000000000055511151231257827021181583404541015625')Дальше скажите, чем заменить сравнение на равенство:
math.isclose(a, b)для обычных вычислений, с явнымиrel_tolиabs_tol, если числа близки к нулю;decimal.Decimalдля денег и всего, что считается в десятичных долях, обязательно из строки:Decimal("0.1"), а не изfloat;fractions.Fraction, когда нужна точная рациональная арифметика;- целые числа в минимальных единицах, копейках или миллисекундах, если задача это позволяет.
Полезно добавить, что дело не в Python: тот же результат даст любой язык с двоичным числом двойной точности.
Где спотыкаются
Говорят, что это баг Python или недостаток языка. Стандарт общий, и в JavaScript, Java, Go выражение ведёт себя так же, а версия про баг закрывает тему вместо объяснения.
Второе: предлагают round(0.1 + 0.2, 2) == round(0.3, 2) как универсальное решение. Для показа пользователю округление годится, но как способ сравнения оно просто сдвигает проблему: пары чисел, лежащие по разные стороны границы округления, по-прежнему сравниваются неверно.
Третье: берут Decimal и создают его из float. Decimal(0.1) честно копирует двоичную погрешность, поэтому корректный путь только один: строка или целое число.