Я не нашел другие ответы удовлетворительными. Конечно, .1
не имеет конечного двоичного расширения, поэтому мы предполагаем, что виновата ошибка представления. Но одна только эта догадка на самом деле не объясняет, почему math.floor(.5/.1)
дает 5.0
, а .5 // .1
дает 4.0
.
Суть в том, что a // b
фактически выполняет floor((a - (a % b))/b)
, а не просто floor(a/b)
.
.5 / .1 равно точно 5,0
Прежде всего, обратите внимание, что результат .5 / .1
равен точно 5.0
в Python. Это так, хотя .1
не может быть точно представлено. Возьмем, например, этот код:
from decimal import Decimal
num = Decimal(.5)
den = Decimal(.1)
res = Decimal(.5/.1)
print('num: ', num)
print('den: ', den)
print('res: ', res)
И соответствующий вывод:
num: 0.5
den: 0.1000000000000000055511151231257827021181583404541015625
res: 5
Это показывает, что .5
может быть представлено конечным бинарным расширением, а .1
— нет. Но это также показывает, что, несмотря на это, результат .5 / .1
равен в точности 5.0
. Это связано с тем, что деление с плавающей запятой приводит к потере точности, и величина, на которую den
отличается от .1
, теряется в процессе.
Вот почему math.floor(.5 / .1)
работает, как и следовало ожидать: поскольку .5 / .1
является 5.0
, запись math.floor(.5 / .1)
аналогична записи math.floor(5.0)
.
Так почему же .5 // .1
не дает 5?
Можно предположить, что .5 // .1
— это сокращение от floor(.5 / .1)
, но это не так. Как оказалось, семантика отличается. И это несмотря на то, что PEP говорит:
Деление по этажам будет реализовано во всех числовых типах Python и будет иметь семантику
a // b == floor(a/b)
Как оказалось, семантика .5 // .1
фактически эквивалентна:
floor((.5 - mod(.5, .1)) / .1)
где mod
— остаток с плавающей запятой от .5 / .1
, округленный до нуля. Это станет ясно из прочтения исходного кода Python.
Здесь возникает проблема из-за того, что .1
не может быть точно представлено двоичным расширением. Остаток с плавающей запятой от .5 / .1
не равен нулю:
>>> .5 % .1
0.09999999999999998
и логично, что это не так. Поскольку двоичное представление .1
немного больше фактического десятичного числа .1
, наибольшее целое число alpha
, такое что alpha * .1 <= .5
(в нашей математике с конечной точностью), равно alpha = 4
. Таким образом, mod(.5, .1)
не равно нулю и примерно равно .1
. Следовательно, floor((.5 - mod(.5, .1)) / .1)
становится floor((.5 - .1) / .1)
, становится floor(.4 / .1)
, что равно 4
.
И именно поэтому .5 // .1 == 4
.
Почему //
делает это?
Поведение a // b
может показаться странным, но есть причина его расхождения с math.floor(a/b)
. В своем блоге об истории Python, Гвидо пишет:
Операция целочисленного деления (//) и родственная ей операция по модулю (%), идут вместе и удовлетворяют хорошему математическому соотношению (все переменные являются целыми числами):
a/b = q with remainder r
такой, что
b*q + r = a and 0 <= r < b
(при условии, что a и b >= 0).
Теперь Гвидо предполагает, что все переменные являются целыми числами, но эта связь сохраняется, если a
и b
являются числами с плавающей запятой, if q = a // b
. Если q = math.floor(a/b)
, отношение не будет в общем случае. И поэтому //
может быть предпочтительнее, потому что он удовлетворяет этому прекрасному математическому соотношению.
person
jme
schedule
20.08.2015