Ответ 1
Я не нашел других ответов, удовлетворяющих. Конечно, .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)
. В своем blog в истории Python Гвидо пишет:
Операция целочисленного деления (//) и ее родство, по модулю (%), идти вместе и удовлетворять хорошим математическим (все переменные являются целыми):
a/b = q with remainder r
такое, что
b*q + r = a and 0 <= r < b
(предполагая, что a и b are >= 0).
Теперь, Guido предполагает, что все переменные являются целыми числами, но это соотношение будет сохраняться, если a
и b
являются float, если q = a // b
. Если q = math.floor(a/b)
, связь вообще не будет выполняться. И поэтому //
может быть предпочтительнее, потому что он удовлетворяет этой хорошей математической взаимосвязи.