为什么 math.floor(x/y) != x // y 对于 Python 中的两个可整除的浮点数?

Why is math.floor(x/y) != x // y for two evenly divisible floats in 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 都是浮点数,因此在 python3+ 上的结果应该相同。如果存在一些浮点错误,其中 x/y 实际上是 4.999999999999999math.floor(4.999999999999999) == 4.0 不会反映在 x/y 中吗?

但是,以下类似情况不受影响:

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

那是因为

>>> .1
0.10000000000000001

.1无法用二进制精确表示

你也可以看到

>>> .5 / 0.10000000000000001
5.0

问题是 Python 会将输出四舍五入为 described here。由于 0.1 不能精确地用二进制表示,因此结果类似于 4.999999999999999722444243844000。当不使用格式时,自然会变成 5.0

我没有发现其他答案令人满意。当然,.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 是 shorthand 对应 floor(.5 / .1),但事实并非如此。事实证明,语义不同。即使 PEP says:

Floor division will be implemented in all the Python numeric types, and will have the semantics of

    a // b == floor(a/b)

事实证明,.5 // .1 的语义 实际上 等同于:

floor((.5 - mod(.5, .1)) / .1)

其中 mod.5 / .1 的浮点余数,四舍五入为零。通过阅读 Python source code.

可以清楚地了解这一点

This.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) 的差异是有原因的。 Guido 在他关于 [=126= 的历史的 blog 中写道:

The integer division operation (//) and its sibling, the modulo operation (%), go together and satisfy a nice mathematical relationship (all variables are integers):

a/b = q with remainder r

such that

b*q + r = a and 0 <= r < b

(assuming a and b are >= 0).

现在,Guido 假设所有变量都是整数,但如果 ab 是浮点数,这种关系仍然成立,if q = a // b.如果 q = math.floor(a/b) 关系 一般不会 成立。所以 // 可能是首选,因为它满足这个很好的数学关系。

This isn't correct, I'm afraid. .5 / .1 is 5.0 exactly. See: (.5/.1).as_integer_ratio(), which yields (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 == 53602879701896397 / 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,相似但不相同的结果。