我首先在 Python 中遇到了这种现象,但结果证明这是常见的答案,例如 MS Excel 给出了这个。 Wolfram Alpha 给出了一个有趣的 split 样答案,它指出零的有理近似为 1/5。 ( 1.0 mod 0.1 )

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

def myFmod(a,n):
    return a - floor(a/n) * n

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

最佳答案

因为 0.1 不是 0.1;该值不能用 double 表示,因此它会四舍五入到最接近的 double 数,即:

0.1000000000000000055511151231257827021181583404541015625

当您调用 fmod 时,您会得到除以上面列出的值的余数,这正是:
0.0999999999999999500399638918679556809365749359130859375

打印时四舍五入为 0.1 (或者可能是 0.09999999999999995 )。

换句话说,fmod 工作得很好,但你没有给它你认为的输入。

编辑: 您自己的实现为您提供了正确答案,因为它不太准确,信不信由你。首先,请注意 fmod 计算余数时没有任何舍入误差;不准确的唯一来源是使用值 0.1 引入的表示错误。现在,让我们来看看您的实现,看看它产生的舍入误差是如何完全抵消表示误差的。

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

首先我们评估 1.0/n ,其中 n 是最接近 0.1 的 double 近似值,如上所示。这种划分的结果大约是:
9.999999999999999444888487687421760603063276150363492645647081359...

请注意,此值不是可表示的 double 数——因此它会四舍五入。要了解这种舍入是如何发生的,让我们看看二进制而不是十进制的数字:
1001.1111111111111111111111111111111111111111111111111 10110000000...

空格表示四舍五入到 double 发生的位置。由于圆点之后的部分大于精确的中间点,因此该值向上取整为 10

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

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

同样,该值不能表示为 double 值,因此在空格指示的位置四舍五入。这次它精确地四舍五入到 1.0 ,所以最终的计算是 1.0 - 1.0 ,当然是 0.0

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

关于python - 为什么 fmod(1.0,0.1) == .1?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/4218961/

10-12 13:49