Я знаю, что большинство десятичных чисел не имеют точного представления с плавающей запятой (математика с плавающей запятой не работает? ).
Но я не понимаю , почему 4*0.1
печатается хорошо , как 0.4
, но 3*0.1
это не так , когда оба значения фактически имеют уродливые десятичные представления:
>>> 3*0.1
0.30000000000000004
>>> 4*0.1
0.4
>>> from decimal import Decimal
>>> Decimal(3*0.1)
Decimal('0.3000000000000000444089209850062616169452667236328125')
>>> Decimal(4*0.1)
Decimal('0.40000000000000002220446049250313080847263336181640625')
0.3000000000000000444089209850062616169452667236328125
как0.30000000000000004
и ,0.40000000000000002220446049250313080847263336181640625
как.4
даже если они появляются , чтобы иметь такую же точность, и , таким образом , не дает ответа на вопрос.Ответы:
Простой ответ заключается в том, что
3*0.1 != 0.3
из-за ошибки квантования (округления) (тогда4*0.1 == 0.4
как умножение на степень два обычно является «точной» операцией).Вы можете использовать
.hex
метод в Python для просмотра внутреннего представления числа (в основном, точное двоичное значение с плавающей запятой, а не приближение по основанию-10). Это может помочь объяснить, что происходит под капотом.0,1 - это 0x1,999999999999a, умноженное на 2 ^ -4. «A» в конце означает цифру 10 - другими словами, 0,1 в двоичной переменной с плавающей запятой очень немного больше, чем «точное» значение 0,1 (потому что конечное 0x0,99 округляется до 0x0.a). Когда вы умножаете это на 4, степень двойки, показатель степени смещается вверх (с 2 ^ -4 до 2 ^ -2), но в остальном число остается неизменным, поэтому
4*0.1 == 0.4
.Однако при умножении на 3 небольшая маленькая разница между 0x0,99 и 0x0.a0 (0x0,07) увеличивается до ошибки 0x0,15, которая отображается как ошибка с одной цифрой в последней позиции. Это приводит к тому, что 0,1 * 3 будет немного больше округленного значения 0,3.
Число с плавающей запятой в Python 3
repr
рассчитано на круговое переключение , то есть отображаемое значение должно быть точно преобразовано в исходное значение. Таким образом, он не может отображаться0.3
и0.1*3
точно так же, иначе два разных числа будут одинаковыми после кругового отключения. Следовательно,repr
движок Python 3 выбирает для отображения один с небольшой очевидной ошибкой.источник
.hex()
; я не знал, что он существовал.)e
потому что это уже шестнадцатеричная цифра. Может быть,p
для власти, а не экспоненты .p
в этом контексте восходит (по крайней мере) к C99, а также появляется в IEEE 754 и на других языках (включая Java). Когдаfloat.hex
иfloat.fromhex
были реализованы (мной :-), Python просто копировал то, что к тому времени стало практикой. Я не знаю, было ли намерение «p» для «Силы», но это кажется хорошим способом обдумать это.repr
(иstr
в Python 3) выдаст столько цифр, сколько требуется, чтобы сделать значение однозначным. В этом случае результат умножения3*0.1
не является ближайшим значением к 0,3 (0x1,3333333333333p-2 в шестнадцатеричном формате), это фактически на один младший бит выше (0x1,3333333333334p-2), поэтому ему нужно больше цифр, чтобы отличить его от 0,3.С другой стороны, умножение
4*0.1
делает получить наиболее близкое значение 0,4 (0x1.999999999999ap-2 в шестнадцатеричной форме ), так что не требуется никаких дополнительных цифр.Вы можете проверить это довольно легко:
Я использовал шестнадцатеричное обозначение выше, потому что это красиво и компактно и показывает разницу в битах между двумя значениями. Вы можете сделать это самостоятельно, например
(3*0.1).hex()
. Если вы предпочитаете видеть их во всей их десятичной славе, вот вам:источник
3*0.1 == 0.3
и4*0.1 == 0.4
?Вот упрощенный вывод из других ответов.
источник
str
иrepr
одинаковы для чисел с плавающей точкой. Для Python 2.7repr
имеет свойства, которые вы идентифицируете, ноstr
гораздо проще - он просто вычисляет 12 значащих цифр и создает на их основе строку вывода. Для Python <= 2,6 обаrepr
иstr
основаны на фиксированном количестве значащих цифр (17 дляrepr
, 12 дляstr
). (И никому нет дела до Python 3.0 или Python 3.1 :-)repr
за того, что поведение Python 2.7 будет идентичным ...Не совсем специфично для реализации Python, но должно применяться к любым функциям с плавающей запятой для десятичных строковых функций.
Число с плавающей запятой, по сути, является двоичным числом, но в научной записи с фиксированным пределом значащих цифр.
Обращение любого числа, имеющего множитель простого числа, которое не используется совместно с основанием, всегда приводит к повторяющемуся представлению точки. Например, 1/7 имеет главный множитель 7, который не используется совместно с 10, и, следовательно, имеет повторяющееся десятичное представление, и то же самое верно для 1/10 с простыми множителями 2 и 5, причем последний не делится с 2 ; это означает, что 0.1 не может быть точно представлено конечным числом битов после точки точки.
Поскольку 0.1 не имеет точного представления, функция, которая преобразует аппроксимацию в строку десятичной точки, обычно пытается аппроксимировать определенные значения, чтобы они не получали неинтуитивных результатов, таких как 0.1000000000004121.
Так как с плавающей запятой находится в научной нотации, любое умножение на степень основания влияет только на экспонентную часть числа. Например, 1.231e + 2 * 100 = 1.231e + 4 для десятичной записи, а также 1.00101010e11 * 100 = 1.00101010e101 в двоичной записи. Если я умножу на не-мощность базы, значимые цифры также будут затронуты. Например, 1.2e1 * 3 = 3.6e1
В зависимости от используемого алгоритма он может попытаться угадать общие десятичные дроби, основываясь только на значащих цифрах. И 0.1, и 0.4 имеют одинаковые значащие цифры в двоичном виде, потому что их числа с плавающей точкой по сути являются усечениями (8/5) (2 ^ -4) и (8/5) (2 ^ -6) соответственно. Если алгоритм идентифицирует шаблон 8/5 sigfig как десятичное число 1.6, то он будет работать с 0.1, 0.2, 0.4, 0.8 и т. Д. Он может также иметь магические шаблоны sigfig для других комбинаций, таких как число с плавающей точкой 3, деленное на число с плавающей точкой 10 и другие магические паттерны, статистически вероятные при делении на 10.
В случае 3 * 0,1 последние несколько значащих цифр, вероятно, будут отличаться от деления числа с плавающей точкой на число с плавающей точкой 10, что приведет к тому, что алгоритм не сможет распознать магическое число для константы 0,3 в зависимости от его допуска к потере точности.
Изменить: https://docs.python.org/3.1/tutorial/floatingpoint.html
Не допускается потеря точности, если значение с плавающей запятой x (0.3) не равно точно числу с плавающей запятой y (0,1 * 3), тогда repr (x) точно не равно repr (y).
источник