Почему math.floor(x/y)!= X//y для двух равномерно делящихся поплавков в Python?

Я читал о делении и целочисленном делении в Python и различиях между делениями в Python2 и Python3. По большей части все имеет смысл. Python 2 использует целочисленное деление только тогда, когда оба значения являются целыми числами. Python 3 всегда выполняет истинное деление. Python 2.2+ ввел оператор // для целочисленного деления.

Примеры других программистов предложили работу хорошо и аккуратно, например:

>>> 1.0 // 2.0      # floors result, returns float
0.0
>>> -1 // 2         # negatives are still floored
-1

Как реализовано //? Почему происходит следующее:

>>> import math
>>> x = 0.5 
>>> y = 0.1
>>> x / y
5.0
>>> math.floor(x/y)
5.0
>>> x // y
4.0

Не стоит x // y = math.floor(x/y)? Эти результаты были получены на python2.7, но поскольку x и y оба являются float, результаты должны быть одинаковыми на python3+. Если есть ошибка с плавающей запятой, где x/y на самом деле 4.999999999999999 и math.floor(4.999999999999999) == 4.0 не будет отображаться в x/y?

Однако следующие аналогичные случаи не затрагиваются:

>>> (.5*10) // (.1*10)
5.0
>>> .1 // .1
1.0

Ответы

Ответ 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), связь вообще не будет выполняться. И поэтому // может быть предпочтительнее, потому что он удовлетворяет этой хорошей математической взаимосвязи.

Ответ 2

Это потому, что

>>> .1
0.10000000000000001

.1 не может быть точно представлен в двоичном формате

Вы также можете видеть, что

>>> .5 / 0.10000000000000001
5.0

Ответ 3

Проблема заключается в том, что Python будет обрабатывать вывод описанный здесь. Так как 0.1 не может быть представлен точно в двоичном формате, результат будет выглядеть как 4.999999999999999722444243844000. Естественно, это становится 5.0, когда не используется формат.

Ответ 4

Это неправильно, я боюсь..5/.1 - 5.0. Смотрите: (.5/.1).as_integer_ratio(), что дает (5,1).

Да, 5 может быть представлен как 5/1, это правда. Но чтобы увидеть долю фактического результата, получаемого Python из-за неточного представления, следуйте за ним.

Во-первых, импорт:

from decimal import *
from fractions import Fraction

Переменные для удобного использования:

// as_integer_ratio() returns a tuple
xa = Decimal((.5).as_integer_ratio()[0])
xb = Decimal((.5).as_integer_ratio()[1])
ya = Decimal((.1).as_integer_ratio()[0])
yb = Decimal((.1).as_integer_ratio()[1])

Устанавливает следующие значения:

xa = 1
xb = 2
ya = 3602879701896397
yb = 36028797018963968

Естественно, 1/2 == 5 и 3602879701896397 / 36028797018963968 == 0.1000000000000000055511151231.

Итак, что происходит, когда мы делимся?

>>> print (xa/xb)/(ya/yb)
4.999999999999999722444243845

Но когда мы хотим, чтобы целочисленное отношение...

>>> print float((xa/xb)/(ya/yb)).as_integer_ratio()
(5, 1)

Как было сказано ранее, 5, конечно, 5/1. Это где Fraction входит:

>>> print Fraction((xa/xb)/(ya/yb))
999999999999999944488848769/200000000000000000000000000

И wolfram alpha подтверждает, что это действительно 4.999999999999999722444243845.


Почему бы вам просто не сделать Fraction(.5/.1) или Fraction(Decimal(.5)/Decimal(.1))?

Последний даст нам тот же результат 5/1. Первый из них даст нам 1249999999999999930611060961/250000000000000000000000000. В результате получается 4.999999999999999722444243844, аналогичный, но не тот же результат.