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