在R中,为什么-1 + 1不等于0?

3

请问有人能帮忙解释一下为什么-1 + 1 ≠ 0吗?

请问有人能帮我理解为什么当我使用内置函数consum()、我自己写的函数ct()和Excel计算同一个东西时,会得到三个不同的值?

现在,我很确定答案是一个“四舍五入”的问题,但我想不出这个问题的部分原因在哪里。我的意思是,这看起来很简单。

在R中,当我生成序列'a'并运行cumsum(a)时,我没有得到期望得到的结果0。如果我尝试使用函数计算相同的值,则会得到一个不同的答案。最后,如果我尝试使用Excel计算相同的值,则会得到第三个答案。

这是我使用cumsum()得到的结果:

> a<- seq(-1, 1, by=.1)
> a
 [1] -1.0 -0.9 -0.8 -0.7 -0.6 -0.5 -0.4 -0.3 -0.2 -0.1  0.0  0.1  0.2  0.3
[15]  0.4  0.5  0.6  0.7  0.8  0.9  1.0
> cumsum(a)
 [1] -1.000000e+00 -1.900000e+00 -2.700000e+00 -3.400000e+00 -4.000000e+00
 [6] -4.500000e+00 -4.900000e+00 -5.200000e+00 -5.400000e+00 -5.500000e+00
[11] -5.500000e+00 -5.400000e+00 -5.200000e+00 -4.900000e+00 -4.500000e+00
[16] -4.000000e+00 -3.400000e+00 -2.700000e+00 -1.900000e+00 -1.000000e+00
[21]  1.110223e-15

我写了一个快速的函数来测试这个问题,本以为会得到相同的答案(或者是0),但实际上得到了完全不同的答案。以下是我的函数以及其结果:

ct<- function(x){
        result = 0
        for(i in 1:length(x)){
           cat(i, ": Result = ", result, " + ", x[i], " = ", result + x[i], "\n")
           result = result + x[i]
        }
}

> ct(a)
1 : Result =  0  +  -1  =  -1 
2 : Result =  -1  +  -0.9  =  -1.9 
3 : Result =  -1.9  +  -0.8  =  -2.7 
4 : Result =  -2.7  +  -0.7  =  -3.4 
5 : Result =  -3.4  +  -0.6  =  -4 
6 : Result =  -4  +  -0.5  =  -4.5 
7 : Result =  -4.5  +  -0.4  =  -4.9 
8 : Result =  -4.9  +  -0.3  =  -5.2 
9 : Result =  -5.2  +  -0.2  =  -5.4 
10 : Result =  -5.4  +  -0.1  =  -5.5 
11 : Result =  -5.5  +  0  =  -5.5 
12 : Result =  -5.5  +  0.1  =  -5.4 
13 : Result =  -5.4  +  0.2  =  -5.2 
14 : Result =  -5.2  +  0.3  =  -4.9 
15 : Result =  -4.9  +  0.4  =  -4.5 
16 : Result =  -4.5  +  0.5  =  -4 
17 : Result =  -4  +  0.6  =  -3.4 
18 : Result =  -3.4  +  0.7  =  -2.7 
19 : Result =  -2.7  +  0.8  =  -1.9 
20 : Result =  -1.9  +  0.9  =  -1 
21 : Result =  -1  +  1  =  4.440892e-16

如果我将for循环中的最后一行更改为以下内容,则可以得到预期的答案0:
result = round(result + x[I], digits = 2)

在Excel中,使用与我的ct()函数相同的逻辑,我得到了最终结果为-2.886580E-15(未对值进行四舍五入)。
2个回答

5
这是使用固定精度表示值的本质,它不能准确地表示这些值。就像 1/3 无法用固定数量的小数位准确表示一样,0.1 也无法用固定数量的二进制位准确表示。因此,就像 3 x (1/3) 在固定数量的小数位上永远无法给出 1 一样,在固定精度的二进制中添加多个 0.1 永远不会准确得到 1。
因此,让我们看一下六位精度的十进制表示,以更清楚地了解这一点(this 用于表示值,而不是表示):
1 -> 1.000000
1/3 -> .333333
2/3 -> .666667
3 -> 3.000000
这样得到: 1/3 + 2/3 -> 0.333333 + 0.666667 -> 1.000000 -> 1(好极了) 1/3 + 1/3 -> 0.333333 + 0.333333 -> 0.666666(不是 2/3,好吧) 3 * 1/3 -> 3.00000 * 0.333333 -> .999999(不是 1,好吧)
如何处理这个问题取决于你,但这应该是预期的行为。
回答你最后一个问题,为什么用两种不同的方式做“相同的事情”会产生不同的结果,这是由中间舍入造成的。如果你曾经用计算器进行过计算,并写下了一些部分中间结果,你就知道写下哪些中间结果可能会有所区别。

3
同样有用的是,R常见问题解答中的第7.31条,“为什么R不认为这些数字相等” - r2evans
2
我会给任何引用计算机科学家应该了解的浮点运算知识的内容点赞,即使是间接引用。 - David Schwartz
2
为什么这些数字不相等? - Pierre L
@DavidSchwartz,也许我在安抚许多读者的不耐烦情绪。但真正的安抚应该是直接粘贴文本而不是链接... - r2evans
好的,我已经阅读了这些链接并且开始仔细阅读ACM PDF。这些基本上为我解答了为什么-1 +1 <> 0。我希望能够得到一些见解,为什么我的函数ct(a)cumsum(a)不会给出相同的答案,因为它们执行的是相同的操作(不是吗?)。 - dave
@dave 请看我的答案更新。当你四舍五入哪些中间结果会影响最终结果时,就会出现这种情况。例如,如果代码写成 (A+B)+(C+D),可能会对 (A+B) 进行四舍五入,也可能对 (C+D) 进行四舍五入,或者两者都进行四舍五入,或者两者都不进行四舍五入,这取决于浮点寄存器的可用性以及在代码开始时哪些变量的组合已经在寄存器中。 - David Schwartz

0
我猜这只是一个四舍五入问题。如果你使用seq.int函数从-10到10生成一个向量,然后执行cumsum操作,你会得到一个总和为0的结果:
> seq.int(-10,10,1)
[1] -10  -9  -8  -7  -6  -5  -4  -3  -2  -1   0   1   2   3   4   5   6   7   8   9  10
> cumsum(seq.int(-10,10,1))
[1] -10 -19 -27 -34 -40 -45 -49 -52 -54 -55 -55 -54 -52 -49 -45 -40 -34 -27 -19 -10   0

如果你真的想在-1和1之间进行序列操作,那么只需将整数序列除以10L

cumsum(seq.int(-10,10,1)/10L)
[1] -1.0 -1.9 -2.7 -3.4 -4.0 -4.5 -4.9 -5.2 -5.4 -5.5 -5.5 -5.4 -5.2 -4.9 -4.5 -4.0 -3.4 -2.7
[19] -1.9 -1.0  0.0

你仍然需要处理一些舍入误差,但这似乎低于R舍入为0的阈值。


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