2012-02-29 105 views
197

下面的代码显然是错误的。有什么问题?这些数字为什么不相等?

i <- 0.1 
i <- i + 0.05 
i 
## [1] 0.15 
if(i==0.15) cat("i equals 0.15") else cat("i does not equal 0.15") 
## i does not equal 0.15 
+5

参见HTTP: //stackoverflow.com/q/6874867和http://stackoverflow.com/q/2769510。 [R Inferno](http://www.burns-stat.com/pages/Tutor/R_inferno.pdf)也是一个很好的阅读。 – Aaron 2012-03-01 02:10:58

回答

261

一般(与语言无关的)原因

因为不是所有的数字可以准确地在IEEE floating point arithmetic来表示(即几乎所有的计算机用来表示十进制数和做数学与他们的标准),你不会总是得到你的预期。这是特别真实的,因为一些简单的有限小数(例如0.1和0.05)的值在计算机中没有精确地表示,所以对它们的算术结果可能不会给出与直接表示“已知“答案。

这是计算机算术的公知的限制和在几个地方讨论:

比较标量

标准的解决方案,这在R是不使用==,而是all.equal功能。或者说,因为all.equal提供了有关差异的详细信息,如果有的话,isTRUE(all.equal(...))

if(isTRUE(all.equal(i,0.15))) cat("i equals 0.15") else cat("i does not equal 0.15") 

产生

i equals 0.15 

使用all.equal代替==的一些例子(最后一个示例是应该表明,这种将正确显示差异)。

0.1+0.05==0.15 
#[1] FALSE 
isTRUE(all.equal(0.1+0.05, 0.15)) 
#[1] TRUE 
1-0.1-0.1-0.1==0.7 
#[1] FALSE 
isTRUE(all.equal(1-0.1-0.1-0.1, 0.7)) 
#[1] TRUE 
0.3/0.1 == 3 
#[1] FALSE 
isTRUE(all.equal(0.3/0.1, 3)) 
#[1] TRUE 
0.1+0.1==0.15 
#[1] FALSE 
isTRUE(all.equal(0.1+0.1, 0.15)) 
#[1] FALSE 

一些细节,直接从answer to a similar question复制:

您遇到的问题是,浮点不能代表小数正是在大多数情况下,这意味着你会经常发现完全匹配失败。

,当你说,而[R略有在于:

1.1-0.2 
#[1] 0.9 
0.9 
#[1] 0.9 

你可以找出什么是真正想在十进制:

sprintf("%.54f",1.1-0.2) 
#[1] "0.900000000000000133226762955018784850835800170898437500" 
sprintf("%.54f",0.9) 
#[1] "0.900000000000000022204460492503130808472633361816406250" 

你可以看到这些数字是不同的,但表示是有点笨拙。如果我们以二进制看他们(当然,十六进制,相当于),我们得到一个更清晰的画面:

sprintf("%a",0.9) 
#[1] "0x1.ccccccccccccdp-1" 
sprintf("%a",1.1-0.2) 
#[1] "0x1.ccccccccccccep-1" 
sprintf("%a",1.1-0.2-0.9) 
#[1] "0x1p-53" 

你可以看到,他们通过2^-53不同,这很重要,因为这个数字之间的最小可表示差两个数字的值接近1,就像这样。

我们可以找出任何给定的计算机此表示的最小的数是通过寻找R中的machine场的关系:

​​

你可以利用这一点来创建一个“几乎等于”功能,将检查的区别接近于浮点中最小的可表示数字。实际上这已经存在:all.equal

?all.equal 
#.... 
#all.equal(x,y) is a utility to compare R objects x and y testing ‘near equality’. 
#.... 
#all.equal(target, current, 
#  tolerance = .Machine$double.eps^0.5, 
#  scale = NULL, check.attributes = TRUE, ...) 
#.... 

所以all.equal功能实际上是检查该数字之间的差为2个尾数之间的最小差值的平方根。

这个算法在一个非常小的称为denormals的数字附近有点搞笑,但是您不必担心这个问题。

比较矢量

上面的讨论假定两个单值的比较。在R中,没有标量,只有向量和隐式向量化是语言的一个优势。为了比较矢量元素的价值,以前的原则成立,但实施略有不同。 ==被矢量化(进行元素比较),而all.equal将整个矢量作为单个实体进行比较。

使用前面的例子

a <- c(0.1+0.05, 1-0.1-0.1-0.1, 0.3/0.1, 0.1+0.1) 
b <- c(0.15,  0.7,   3,  0.15) 

==不给“预期”的结果和all.equal不进行逐元素

a==b 
#[1] FALSE FALSE FALSE FALSE 
all.equal(a,b) 
#[1] "Mean relative difference: 0." 
isTRUE(all.equal(a,b)) 
#[1] FALSE 

相反,它循环通过两个向量的一个版本必须可使用

mapply(function(x, y) {isTRUE(all.equal(x, y))}, a, b) 
#[1] TRUE TRUE TRUE FALSE 

如果功能这个版本需要,可以将它写入

elementwise.all.equal <- Vectorize(function(x, y) {isTRUE(all.equal(x, y))}) 

可以称为只是

elementwise.all.equal(a, b) 
#[1] TRUE TRUE TRUE FALSE 

或者,而不是更加的函数调用包装all.equal,你可以复制的相关内部all.equal.numeric和使用隐式矢量化:

tolerance = .Machine$double.eps^0.5 
# this is the default tolerance used in all.equal, 
# but you can pick a different tolerance to match your needs 

abs(a - b) < tolerance 
#[1] TRUE TRUE TRUE FALSE 
32

添加到布莱恩的评论(这是什么原因)你也能在由我们来这荷兰国际集团all.equal代替:

# i <- 0.1 
# i <- i + 0.05 
# i 
#if(all.equal(i, .15)) cat("i equals 0.15\n") else cat("i does not equal 0.15\n") 
#i equals 0.15 

这里每约书亚的警告是更新的代码(感谢Joshua):

i <- 0.1 
i <- i + 0.05 
i 
if(isTRUE(all.equal(i, .15))) { #code was getting sloppy &went to multiple lines 
    cat("i equals 0.15\n") 
} else { 
    cat("i does not equal 0.15\n") 
} 
#i equals 0.15 
+0

我错过了Brian的链接,这简洁地解释了我的回答。 – 2012-02-29 23:57:21

+14

当有差异时'all.equal'不会返回'FALSE',所以当你在'if'语句中使用''时,你需要用'isTRUE'来包装它。 – 2012-03-01 00:49:18

7

这是hackish的,但快速:

if(round(i, 10)==0.15) cat("i equals 0.15") else cat("i does not equal 0.15")