为什么fmod(1.0,0.1)== .1?

bee*_*tee 4 python math floating-point

我首先在Python中经历过这种现象,但事实证明这是常见的答案,例如MS Excel给出了这一点.Wolfram Alpha给出了一个有趣的精神回答,它指出零的有理逼近是1/5.(1.0 mod 0.1)

另一方面,如果我手动实现定义,它会给我'正确'的答案(0).

def myFmod(a,n):
    return a - floor(a/n) * n
Run Code Online (Sandbox Code Playgroud)

这里发生了什么.我错过了什么吗?

Ste*_*non 23

因为0.1不是0.1; 该值不能以双精度表示,因此它会四舍五入到最接近的双精度数,这正是:

0.1000000000000000055511151231257827021181583404541015625
Run Code Online (Sandbox Code Playgroud)

当你打电话时fmod,你得到除以上面列出的值的剩余部分,这恰好是:

0.0999999999999999500399638918679556809365749359130859375
Run Code Online (Sandbox Code Playgroud)

当你打印时,它会转向0.1(或者可能0.09999999999999995).

换句话说,fmod效果很好,但你没有给它你认为你的输入.


编辑:你自己的实现给你正确的答案,因为它不太准确,信不信由你.首先,注意fmod计算余数而没有任何舍入误差; 唯一不准确的来源是使用该值引入的表示错误0.1.现在,让我们逐步完成您的实现,并查看它产生的舍入错误如何完全取消表示错误.

一次评估a - floor(a/n) * n一步,跟踪每个阶段计算的确切值:

首先我们评估1.0/n,如上所示,n最接近的双精度近似值在哪里0.1.这种划分的结果大约是:

9.999999999999999444888487687421760603063276150363492645647081359...
Run Code Online (Sandbox Code Playgroud)

请注意,此值不是可表示的双精度数 - 因此它将被舍入.要查看这种舍入是如何发生的,让我们看一下二进制而不是十进制的数字:

1001.1111111111111111111111111111111111111111111111111 10110000000...
Run Code Online (Sandbox Code Playgroud)

空格表示舍入到双精度的位置.由于圆点之后的部分大于精确的中间点,因此该值精确地向上舍入10.

floor(10.0)可以预见的是10.0.所以剩下的就是计算了1.0 - 10.0*0.1.

在二进制中,确切的值10.0 * 0.1是:

1.0000000000000000000000000000000000000000000000000000 0100
Run Code Online (Sandbox Code Playgroud)

同样,该值不能表示为double,因此在空格指示的位置处舍入.这次它精确地向下舍入1.0,因此最后的计算是1.0 - 1.0,这当然是0.0.

您的实现包含两个舍入错误,恰好0.1在这种情况下抵消了值的表示错误. fmod相比之下,总是精确的(至少在具有良好数值库的平台上),并暴露出表示错误0.1.