Целочисленное деление всегда равно полу регулярного деления? - PullRequest
0 голосов
/ 22 декабря 2018

Для больших отношений целочисленное деление (//) не обязательно должно быть равно полу обычного деления (math.floor(a/b)).

Согласно документации Python (* 1006)* - 6,7),

деление целых чисел по полу дает целое число;результатом является математическое деление с применением функции «floor» к результату.

Однако,

math.floor(648705536316023400 / 7) = 92672219473717632

648705536316023400 // 7 = 92672219473717628

'{0:.10f}'.format(648705536316023400 / 7) дает '92672219473717632.0000000000', но последние две цифры десятичной части должны быть 28, а не 32.

Ответы [ 3 ]

0 голосов
/ 22 декабря 2018

Возможно, вы имеете дело с целочисленными значениями, которые слишком велики для того, чтобы выражать их как числа с плавающей точкой.Ваше число значительно больше, чем 2 ^ 53, то есть , где промежутки между соседними числами с плавающей запятой начинают становиться больше, чем 1 .Таким образом, вы теряете некоторую точность при выполнении деления с плавающей точкой.

Целочисленное деление, с другой стороны, вычисляется точно.

0 голосов
/ 22 декабря 2018

Ваша проблема в том, что, несмотря на то, что "/" иногда называют "оператором истинного деления" и его имя метода __truediv__, его поведение на целых числах не является "истинным математическим делением".Вместо этого он выдает результат с плавающей запятой, который неизбежно имеет ограниченную точность.

Для достаточно больших чисел даже интегральная часть числа может страдать от ошибок округления с плавающей запятой.Когда 648705536316023400 преобразуется в число с плавающей точкой Python (IEEE double), оно округляется до 648705536316023424 1 .

Кажется, я не могу найти авторитетную документацию о точном поведении операторов встроенных типов в текущем Python.Исходный PEP, который представил функцию, утверждает, что "/" эквивалентно преобразованию целых чисел в число с плавающей запятой и затем выполнению деления с плавающей запятой.Однако быстрый тест в Python 3.5 показывает, что это не так.Если бы это было так, следующий код не дал бы никакого вывода.

for i in range(648705536316023400,648705536316123400):
    if math.floor(i/7) != math.floor(float(i)/7):
        print(i)

Но, по крайней мере, для меня он действительно производит вывод.

Вместо этого мне кажется, что Python выполняет деление начисла в том виде, в котором они представлены, и округление результата до числа с плавающей запятой.Возьмем пример из результатов этих программ.

648705536316123383 // 7                   == 92672219473731911
math.floor(648705536316123383 / 7)        == 92672219473731904
math.floor(float(648705536316123383) / 7) == 92672219473731920
int(float(92672219473731911))             == 92672219473731904

Стандартная библиотека Python предоставляет тип дроби, а оператор деления для дроби, деленной на int, выполняет "истинное математическое деление".

math.floor(Fraction(648705536316023400) / 7) == 92672219473717628
math.floor(Fraction(648705536316123383) / 7) == 92672219473731911

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


Для дальнейшего тестирования моей теории «одно округление против двух» я провел тест со следующим кодом.

#!/usr/bin/python3
from fractions import Fraction
edt = 0
eft = 0
base = 1000000000010000000000
top = base + 1000000
for i in range(base,top):
    ex = (Fraction(i)/7)
    di = (i/7)
    fl = (float(i)/7)
    ed = abs(ex-Fraction(di))
    ef = abs(ex-Fraction(fl))
    edt += ed
    eft += ef
print(edt/10000000000)
print(eft/10000000000)

А средняя величина ошибки была существенно меньше для прямого деления, чем для первого преобразования с плавающей запятой, поддерживая теорию одно округление против двух.

1 Обратите внимание, что печать с плавающей запятой напрямую непокажите его точное значение, вместо этого он покажет самое короткое десятичное число, которое будет округлено до этого значения (что позволяет преобразование без потерь в оба конца из числа с плавающей точкой в ​​строку и обратно в число с плавающей запятой).

0 голосов
/ 22 декабря 2018

Причина, по которой коэффициенты в вашем тестовом примере не равны, заключается в том, что в случае math.floor(a/b) результат вычисляется с помощью арифметики с плавающей запятой (64-битная IEEE-754), что означает максимальную точность.Коэффициент, который у вас есть, больше, чем предел 2 53 , выше которого с плавающей точкой больше нет точности до единицы.

Однако с целочисленным делением Python использует свой неограниченный целочисленный диапазон,и поэтому этот результат верен.

См. также "Семантика истинного деления" в PEP 238 :

Обратите внимание, что для аргументов int и long - истинное делениеможет потерять информацию;это в природе истинного разделения (пока рациональное не в языке).Алгоритмы, которые сознательно используют длинные, должны учитывать использование //, поскольку истинное деление длин сохраняет не более 53 битов (на большинстве платформ).

...