2012-02-29 108 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") 
相關問題