Float-point representation error

11 25 October 2024 at 11:12

  • Ошибка представления чисел с плавающей точкой
  • Мой конспект разбора этой проблемы на русском языке

Комментарии: t.me/zakirovtech/4

Пример

  • проблема возникает при вычислении результата операции

>> 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