S
sanspie's notes
@sanspie_notes834 подп.
1.7Kпросмотров
28 февраля 2025 г.
Score: 1.8K
float и Decimal Вас никогда не удивляло, что 0.1 + 0.2 != 0.3? Почему float считает с погрешностями, и всем норм? Дело в том, что 0.1 выглядит как 0 0111111101 11001100110011001100110011001100110011001100110011010. Где: • 0 обозначает знак + (и 1 обозначает -) • 0111111101 обозначает exponent, равную 0^10 + 2^9 + 2^8 + 2^7 + 2^6 + итд = 1019. Вычтем 1023 (размерность double) и получим итоговое значение: 1019 - 1023 = 4 • 11001100110011001100110011001100110011001100110011010 обозначет "significand" или "мантису", которая равна: 2^-exp + 2^-exp-1 + 2^-exp-2 + итд ~= 0.1 Вот так мы можем примерно представить 0.1 в виде float. Примерно – потому что все вычисления идут с погрешностью. Мы можем проверить данное утверждение, добавив погрешность вручную: >>> assert 0.1 + 2.220446049250313e-18 == 0.1 Значение внешне не изменилось при добавлении погрешности. Посмотрим на sys.float_info.epsilon, который устанавливает необходимый порог для минимальных отличий 1.0 от следующего float числа. >>> import sys >>> sys.float_info.epsilon 2.220446049250313e-16 >>> assert 1.0 + sys.float_info.epsilon > 1.0 >>> assert 1.0 + 2.220446049250313e-17 == 1.0 # число меньше epsilon Как конкретно будет выглядеть 0.1? А вот тут нам уже поможет Decimal для отображения полного числа в десятичной системе: >>> decimal.Decimal(0.1) Decimal('0.1000000000000000055511151231257827021181583404541015625') И вот ответ про 0.1 + 0.2, полное демо с битиками: >>> decimal.Decimal(0.1) Decimal('0.1000000000000000055511151231257827021181583404541015625') >>> decimal.Decimal(0.2) Decimal('0.200000000000000011102230246251565404236316680908203125') >>> decimal.Decimal(0.1 + 0.2) Decimal('0.3000000000000000444089209850062616169452667236328125') >>> decimal.Decimal(0.3) Decimal('0.299999999999999988897769753748434595763683319091796875') Числа не равны друг другу, потому что их разница больше предельной точности float. А сам Decimal может использовать любую точность под задачу. >>> from decimal import Decimal, getcontext >>> getcontext().prec = 6 >>> Decimal(1) / Decimal(7) Decimal('0.142857') >>> getcontext().prec = 28 >>> Decimal(1) / Decimal(7) Decimal('0.1428571428571428571428571429') Но и Decimal не может в абсолютную точность, потому что есть в целом невыразимые в десятичной системе числа, такие как math.pi, ⅓, тд. С чем-то из них может помочь fractions.Fraction для большей точности, но от существования иррациональных чисел никуда не деться. Почему всем норм, что у нас с float такие погрешности в вычислениях? Потому что во многих задачах абсолютная точность недостижима и не имеет смысла. Благодаря плавающей точке мы можем хранить как очень большие, так и очень маленькие числа без существенных затрат памяти. А ещё float - очень быстрый. В том числе за счет аппаратного ускорения. » pyperf timeit -s 'a = 0.1; b = 0.2' 'a + b' ..................... Mean +- std dev: 8.75 ns +- 0.2 ns » pyperf timeit -s 'import decimal; a = decimal.Decimal("0.1"); b = decimal.Decimal("0.2")' 'a + b' ..................... Mean +- std dev: 27.7 ns +- 0.1 ns Разница в 3 раза. Про то, как устроен float внутри – рассказывать не буду. У Никиты Соболева недавно было большое и подробное видео на тему внутреннего устройства float. У него действительно хороший технический контент, советую подписаться: @opensource_findings Итого Если у вас нет требований по работе именно с десятичной записью числа (как, например, в бухгалтерии), то используйте float. Он даст достаточную точность и хорошую скорость. Если вы хотите, чтобы расчеты велись в десятичных цифрах и ваши расчеты построены так, что абсолютная точность достижима, то используйте Decimal. Дополнительные материалы: • https://www.youtube.com/@sobolevn/ • https://0.30000000000000004.com • https://en.wikipedia.org/wiki/X87 • http://aco.ifmo.ru/el_books/numerical_methods/lectures/app_1.html
1.7K
просмотров
3940
символов
Нет
эмодзи
Нет
медиа

Другие посты @sanspie_notes

Все посты канала →
float и Decimal Вас никогда не удивляло, что 0.1 + 0.2 != 0. — @sanspie_notes | PostSniper