#python #string #rounding
#python #строка #округление
Вопрос:
Встроенная в Python функция str() выдает некоторые странные результаты при передаче значений с плавающей запятой со многими десятичными знаками. Вот что происходит:
>>> str(19.9999999999999999)
>>> '20.0'
Я ожидаю получить:
>>> '19.9999999999999999'
Кто-нибудь знает почему? и, возможно, обходной путь?
Спасибо!
Ответ №1:
Дело не str()
в том, что округляется, а в том, что вы используете значения с плавающей точкой в первую очередь. Типы с плавающей точкой быстры, но имеют ограниченную точность; другими словами, они неточны по дизайну. Это относится ко всем языкам программирования. Для получения более подробной информации о причудах с плавающей запятой, пожалуйста, прочитайте «Что каждый программист должен знать об арифметике с плавающей запятой«
Если вы хотите хранить точные числа и оперировать с ними, используйте decimal
модуль:
>>> from decimal import Decimal
>>> str(Decimal('19.9999999999999999'))
'19.9999999999999999'
Комментарии:
1. -1
Decimal
также имеет ограниченную точность (28 десятичных разрядов по умолчанию). Это НЕ волшебное средство от всех болезней. Смотрите мой обновленный ответ.2. Это правда, десятичные типы могут хранить только точные числа, работа с ними приведет к их усечению.
3.
str
также округляет (сравните0.9999999999999999
иstr(0.9999999999999999)
в REPL. Этого можно избежать с помощьюrepr
(хотя это не поможет с округлением из-за неточности значения с плавающей точкой).
Ответ №2:
Значение с плавающей точкой имеет 32 бита (по крайней мере, в C). Один из этих битов выделяется для знака, несколько — для мантиссы, а несколько — для экспоненты. Вы не можете уместить каждую десятичную дробь до бесконечного числа цифр в 32 бита. Поэтому числа с плавающей запятой в значительной степени основаны на округлении.
Если вы попытаетесь str(19.998)
, это, вероятно, даст вам что-то, по крайней мере, близкое к 19.998, потому что 32 бита имеют достаточную точность для оценки этого, но что-то вроде 19.999999999999999 слишком точно для оценки в 32 бита, поэтому оно округляется до ближайшего возможного значения, которое оказывается равным 20.
Комментарии:
1. Совершенно верно, за исключением того, что Python
float
— это Cdouble
, обычно 64 бита.
Ответ №3:
Пожалуйста, обратите внимание, что это проблема понимания чисел с плавающей запятой (фиксированной длины). Большинство языков делают в точности (или очень похоже) то, что делает Python.
Python float
— это 64-разрядный двоичный файл IEEE 754 с плавающей запятой. Его точность ограничена 53 битами, т. е. чуть меньше 16 десятичных разрядов. 19.9999999999999999
содержит 18 десятичных разрядов; он не может быть представлен точно как a float
. float("19.9999999999999999")
выдает ближайшее значение с плавающей запятой, которое совпадает с float("20.0")
.
>>> float("19.9999999999999999") == float("20.0")
True
Если под «многими десятичными знаками» вы подразумеваете «много цифр после десятичной точки», пожалуйста, имейте в виду, что те же «странные» результаты получаются, когда перед десятичной точкой много десятичных цифр.:
>>> float("199999999999999999")
2e 17
Если вам нужна полная float
точность, не используйте str(), используйте repr():
>>> x = 1. / 3.
>>> str(x)
'0.333333333333'
>>> str(x).count('3')
12
>>> repr(x)
'0.3333333333333333'
>>> repr(x).count('3')
16
>>>
Обновление Интересно, как часто decimal
это назначается в качестве панацеи от удивления, вызванного плавающей точкой. Это часто сопровождается простыми примерами вроде 0.1 0.1 0.1 != 0.3
. Никто не останавливается, чтобы указать, что decimal
имеет свою долю недостатков, например
>>> (1.0 / 3.0) * 3.0
1.0
>>> (Decimal('1.0') / Decimal('3.0')) * Decimal('3.0')
Decimal('0.9999999999999999999999999999')
>>>
Правда, float
точность ограничена 53 двоичными разрядами. По умолчанию точность decimal
ограничена 28 десятичными разрядами.
>>> Decimal(2) / Decimal(3)
Decimal('0.6666666666666666666666666667')
>>>
Вы можете изменить предел, но точность по-прежнему ограничена. Вам все еще нужно знать характеристики числового формата, чтобы эффективно использовать его без «поразительных» результатов, а дополнительная точность достигается за счет более медленной работы (если вы не используете cdecimal
модуль 3rd-party).
Ответ №4:
Для любого заданного двоичного числа с плавающей запятой существует бесконечный набор десятичных дробей, которые при вводе округляются до этого числа. Python str
сталкивается с некоторыми трудностями при создании кратчайшей десятичной дроби из этого набора; см. Статью GLS http://kurtstephens.com/files/p372-steele.pdf для общего алгоритма (IIRC они используют уточнение, которое в большинстве случаев позволяет избежать математики произвольной точности). Так получилось, что вы ввели десятичную дробь, которая округляется до значения с плавающей запятой (IEEE double), наименьшая возможная десятичная дробь которого отличается от введенной вами.
Комментарии:
1. (1) «дробь» не имеет значения; это происходит и с очень большими целыми числами. Это repr (), который создает кратчайшее представление, а не str(), и это совсем недавно; repr () раньше использовал 17 десятичных разрядов точности.