整数除法是否总是等于普通除法的向下取整?

33

对于大的商数,整数除法(//)似乎不一定等于普通除法取整后的结果(math.floor(a/b))。

根据 Python 文档 (https://docs.python.org/zh-cn/3/reference/expressions.html - 6.7),

整数的取整除法将得到一个整数;结果是应用了 'floor' 函数后的数学除法结果。

然而,

math.floor(648705536316023400 / 7) = 92672219473717632

648705536316023400 // 7 = 92672219473717628

'{0:.10f}'.format(648705536316023400 / 7) 的结果为 '92672219473717632.0000000000',但小数部分的最后两个数字应该是 28 而不是 32。


3
如果从以下答案中不清楚的话,问题在于当文档用“数学除法”来定义“地板除法”(//)时,“数学除法”并不是指Python中的“除法”(/)。相反,“除法”(/)和“地板除法”(//)是对真正(“数学上的”)除法的两种不同近似。 - ruakh
4个回答

33
您的测试用例中商数不相等的原因是在使用 math.floor(a/b) 时,结果使用浮点运算 (IEEE-754 64位),这意味着有一个最大精度。您得到的商数大于 253 的极限,超过此极限的浮点数就无法准确表达了。
但是在整数除法中,Python 使用其无限整数范围,所以结果是正确的。
另请参阅 PEP 238 中的"真实除法的语义"

请注意,对于 int 和 long 参数,真实除法可能会丢失信息。这是真实除法本质上的问题(只要语言中没有有理数)。有意使用 longs 的算法应该考虑使用 //,因为长整数的真实除法保留的精度不超过 53 位(在大多数平台上)。


2
今天我了解到在Python中,浮点数除法被称作"真除法"。由于定义上的原因,浮点数除法是不精确的,而整数除法则是精确的;所以很奇怪的是Python故意为不精确版本选择了“真除法”这个名字。嗯。 - Quuxplusone

19

10

您的问题是,尽管“/”有时被称为“真除法运算符”,其方法名称为__truediv__,但它在整数上的行为并不是“真正的数学除法”。相反,它会产生一个浮点结果,这个结果不可避免地具有有限的精度。

对于足够大的数字,甚至数字的整数部分都可能受到浮点舍入误差的影响。当648705536316023400转换为Python浮点数(IEEE double)时,它会被舍入为6487055363160234241

我似乎找不到关于当前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标准库提供了一个分数类型和分数除以整数的除法运算符,执行的是“真正的数学除法”。

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

然而,您应该意识到使用分数类型可能会带来严重的性能和内存影响。请记住,分数可以在不增加数量级的情况下增加存储要求。


为了进一步测试我的“一次四舍五入 vs 两次四舍五入”理论,我使用了以下代码进行测试。

#!/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请注意,直接打印浮点数不会显示其精确值,而是显示最短的十进制数,该数将四舍五入为该值(允许从浮点数到字符串再到浮点数的无损往返转换)。


3

在Python文档中,“数学除法”指的是实数上的精确除法。

现在,回到你有关整数除法(又称欧几里得除法)和浮点数除法取底(比“普通除法”更好的术语)的问题,我在2005年研究了这个问题。我证明了对于二进制下的四舍五入,如果x−y恰好可表示,则浮点数除法x/y的向下取整,即math.floor(x/y),等于整数除法。你可以在我的网站HAL上获取论文。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接