About floats
- Ошибка представления чисел с плавающей точкой
- Мой конспект разбора этой проблемы на русском языке
Пример
- проблема возникает при вычислении результата операции
>> 0.1 + 0.2 == 0.3
>> False
>> 0.1 + 0.2
>> 0.30000000000000004
Почему так происходит?
Дроби могут быть конечными и бесконечными:
- одна и та же дробь в одной системе счисления может быть конечной, а в другой нет. Например, 1/10 в десятичной системе имеет конец, по основанию 2 — она бесконечна.
В исходном коде дробное число передается в десятичном виде. Интерпретатор или компилятор должен преобразовать десятичную дробь в двоичный вид, чтобы процессор мог ее обработать.
Если дробь бесконечна в двоичной системе исчисления, при ее преобразовании возникает неточность. Процессор получает неточный машинный код и возвращает неточный ответ, который преобразуется обратно в дробное число с большой последовательностью чисел после точки.
Память устройства и ресурсы процессора конечны, поэтому такое число округляется. Округление зависит от архитектуры системы. В системе с 64 битной архитектурой, число округляется до 17 знаков после точки.
- большую последовательность дробного числа можно посмотреть преобразовав его в тип Decimal:
from decimal import Decimal
Decimal(0.3)
Decimal('0.299999999999999988897769753748434595763683319091796875')
Как понять, что дробь имеет конец в двоичной системе
0.3 → 3/10 — знаменатель дроби 10 не равен значению возведения 2 в какую либо степень. Значит в двоичной системе эта дробь не имеет конца. Преобразование будет не точным.
- Процессор преобразует бесконечную дробь в число с огромной последовательностью после точки, которое будет округлено до 17 знаков.
Можно проверить:
Decimal(0.3)
0.5 → 5/10 → 1/2 — знаменатель дроби 2 равен по значению возведения 2 в степень 1. Значит в двоичной системе эта дробь имеет конец. Преобразование будет точным.
Можно проверить:
Decimal(0.5)
Как правильно сравнить два числа с плавающей точкой
Для этого можно воспользоваться функцией
isclose()
встроенной библиотеки math.
import math
math.isclose(0.1 + 0.2, 0.3)
True
По умолчанию в
isclose()
задана точность в 9 знаков после запятой. Точность можно изменить передав в именованный параметр rel_tol
отрицательную экспоненту с нужным числом:
>> math.isclose(0.1 + 0.2, 0.3, rel_tol=1e-18)
Если требуется сравнить с двумя операторами (>=), можно воспользоваться
or
: >> math.isclose(0.1 + 0.2, 0.3) or (0.1 + 0.2) > 0.3
Еще один способ сравнивать числа с плавающей точкой, это использовать встроенную функцию
round()
передав второй параметр, чтобы округлить число до необходимого количества знаков после точки, т.е. установив точность вещественного числа вручную.
>> round((0.1 + 0.2), 1) == 0.3
>> True