Почему `0,4/2` равно` 0,2`, между тем `0,6/3` равно $0.19999999999999998` в python?

Я знаю, что это деление по плавающей запятой. Но почему эти две формулы ведут себя по-другому?

И я сделал еще несколько исследований, результат еще более запутывал меня:

>>>0.9/3
0.3

>>>1.2/3
0.39999999999999997

>>>1.5/3
0.5

Какова логика здесь, чтобы решить, будет ли результат печататься с одним десятичным знаком или более?

PS: Я использовал python3.4 для выполнения эксперимента выше.

Ответ 1

Поскольку точные значения результатов с плавающей запятой несколько отличаются.

>>> '%.56f' % 0.4
'0.40000000000000002220446049250313080847263336181640625000'
>>> '%.56f' % (0.4/2)
'0.20000000000000001110223024625156540423631668090820312500'
>>> '%.56f' % 0.6
'0.59999999999999997779553950749686919152736663818359375000'
>>> '%.56f' % (0.6/3)
'0.19999999999999998334665463062265189364552497863769531250'
>>> '%.56f' % 0.2
'0.20000000000000001110223024625156540423631668090820312500'
>>> (0.2 - 0.6/3) == 2.0**-55
True

Как вы можете видеть, результат, который печатается как "0,2", действительно немного ближе к 0,2. Я добавил бит в конце, чтобы показать вам, какое точное значение имеет разница между этими двумя числами. (Если вам интересно, приведенные выше представления являются точными значениями - добавление любого количества цифр за пределы этого просто добавляет больше нулей).

Ответ 2

Ознакомьтесь с документацией по номерам с плавающей запятой в python.

В частности:

Интересно, что существует много разных десятичных чисел, которые имеют одну и ту же ближайшую приближенную двоичную дробь. Например, цифры 0,1 и 0,10000000000000001 и 0,1000000000000000055511151231257827021181583404541015625 все приближаются к 3602879701896397/2 ** 55. Поскольку все эти десятичные значения имеют одинаковое приближение, любой из них может отображаться при сохранении инвариантного eval (repr (x) ) == x.

Исторически, подсказка Python и встроенная функция repr() выбрали бы одну из 17 значащих цифр, 0.10000000000000001. Начиная с Python 3.1, Python (в большинстве систем) теперь может выбрать самый короткий из них и просто отображать 0,1.

Ответ 3

Числа с плавающей запятой реализованы как binary64 в соответствии с IEEE 754 (как на практически всех языках программирования).

Этот стандарт дает 52 бит "значению/доле" (приблизительно 16 десятичных цифр точности), 11 бит до экспоненты и 1 бит к знаку (плюс или минус):

IEEE 754 bits

В частности, число, подобное 0.4, не может быть представлено как

(1 + f) * 2**(exponent)

для некоторой доли в базе 2 и показателя степени, который может быть представлен 11 битами (от -1022 до 1023).

Просмотр 0.4 в шестнадцатеричном формате, например:

>>> (0.4).hex()
'0x1.999999999999ap-2'

мы видим, что наилучшее приближение в нашем наборе чисел есть

+ 2**(-2) * (1 + 0x999999999999a/ float(2**52))

Попытка представить это в базе 2, мы имеем

2**(-2) * (1 + 0.6)

но 0.6 = 9/15 = 1001_2/1111_2, записанный в базе 2, имеет повторяющуюся строку из четырех двоичных цифр

0.1001100011000110001...

поэтому никогда не может быть представлено с использованием конечного числа двоичных цифр.


Немного больше в глубине

Итак, мы можем "распаковать" 0.4

>>> import struct
>>> # 'd' for double, '>' for Big-endian (left-to-right bits)
>>> float_bytes = struct.pack('>d', 0.4)

как 8 байтов (1 байт - 8 бит)

>>> float_bytes
'?\xd9\x99\x99\x99\x99\x99\x9a'

или как 16 шестнадцатеричных цифр (1 шестнадцатеричная цифра - 4 бита, так как 2**4 == 16)

>>> ''.join(['%2x' % (ord(digit),) for digit in float_bytes])
'3fd999999999999a'

или как все 64 бит в своей славе

>>> float_bits = ''.join(['%08d' % (int(bin(ord(digit))[2:]),)
...                       for digit in float_bytes])
>>> float_bits
'0011111111011001100110011001100110011001100110011001100110011010'

Оттуда первый бит является битом знака:

>>> sign = (-1)**int(float_bits[0], 2)
>>> sign
1

Следующие 11 бит являются экспонентой (но сдвинуты на 1023, соглашение binary64):

>>> exponent = int(float_bits[1:1 + 11], 2) - 1023
>>> exponent
-2

Итоговые 52 бит - это дробная часть

>>> fraction = int(float_bits[1 + 11:1 + 11 + 52], 2)
>>> fraction
2702159776422298
>>> hex(fraction)
'0x999999999999a'

Объединяя все это

>>> sign * 2**(exponent) * (1 + fraction / float(2**52))
0.4