Целочисленное деление всегда равно полу регулярного деления?
Для больших отношений целочисленное деление (//
) не обязательно должно быть равно полу обычного деления (math.floor(a/b)
).
Согласно документации Python (https://docs.python.org/3/reference/expressions.html - 6.7),
деление целых чисел по полу дает целое число; Результатом является математическое деление с применением к полу функции пола.
Тем не мение,
math.floor(648705536316023400 / 7) = 92672219473717632
648705536316023400 // 7 = 92672219473717628
'{0:.10f}'.format(648705536316023400/7)
выдает '92672219473717632.0000000000', но последние две цифры десятичной части должны быть 28, а не 32.
Ответы
Ответ 1
Причина, по которой коэффициенты в вашем тестовом примере не равны, заключается в том, что в случае math.floor(a/b)
результат рассчитывается с помощью арифметики с плавающей запятой (64-битная IEEE-754), что означает максимальную точность. Коэффициент, который у вас есть, больше, чем предел 2 53, выше которого плавающая точка больше не точна до единицы.
Однако с помощью целочисленного деления Python использует неограниченный диапазон целых чисел, поэтому результат верен.
См. также "Семантика истинного разделения" в PEP 238:
Обратите внимание, что для аргументов int и long истинное деление может потерять информацию; это в природе истинного разделения (пока рациональное не в языке). Алгоритмы, которые сознательно используют длинные, должны учитывать использование //
, поскольку истинное разделение длин сохраняет не более 53 бит точности (на большинстве платформ).
Ответ 2
Вы можете иметь дело с целочисленными значениями, которые слишком велики для того, чтобы выражать их в точности как числа с плавающей точкой. Ваше число значительно больше, чем 2 ^ 53, в котором промежутки между соседними числами с плавающей запятой начинают становиться больше 1. Таким образом, вы теряете некоторую точность при выполнении деления с плавающей запятой.
Целочисленное деление, с другой стороны, вычисляется точно.
Ответ 3
Ваша проблема в том, что, несмотря на то, что "/" иногда называют "оператором истинного деления" и его именем метода является __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
Однако вы должны знать о потенциально серьезных последствиях для производительности и памяти при использовании типа дроби. Помните, что фракции могут увеличиваться в требованиях к хранению без увеличения величины.
Чтобы дополнительно проверить мою теорию "одно округление против двух", я провел тест со следующим кодом.
#!/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 Обратите внимание, что прямая печать числа с плавающей запятой не показывает его точное значение, вместо этого оно показывает самое короткое десятичное число, которое округляется до этого значения (позволяя преобразование туда и обратно без потерь из числа с плавающей точкой в строку и обратно в число с плавающей точкой).
Ответ 4
Под "математическим делением" документы Python означают точное деление на действительные числа.
Теперь, возвращаясь к вашему вопросу о целочисленном делении (также известном как евклидово деление) по отношению к полу деления с плавающей запятой (лучше, чем "обычное деление"), я изучил эту проблему в 2005 году. Я доказал, что для округления до ближайшего в основание 2, если x − y точно представимо, то пол деления с плавающей точкой x/y, то есть math.floor(x/y)
, равен целочисленному делению. Вы можете получить статью на моем веб-сайте или в HAL.