2012-02-29 28 views
197

Aşağıdaki kod açıkça yanlıştır. Sorun ne? tüm sayılar IEEE floating point arithmetic tam olarak (hemen hemen tüm bilgisayarlarda ondalık sayıları temsil etmek için kullanmak ve onlarla matematik standart) temsil edilebilir beriBu sayılar neden eşit değil?

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

Ayrıca bkz: http: //stackoverflow.com/q/6874867 ve http://stackoverflow.com/q/2769510. [R Inferno] (http://www.burns-stat.com/pages/Tutor/R_inferno.pdf) ayrıca bir başka harika okumadır. – Aaron

cevap

261

Genel (dil agnostik) nedeni

, her zaman olmaz beklediğin şeyi al. Bu, özellikle doğrudur, çünkü basit, sonlu ondalık değerler (örneğin, 0.1 ve 0.05), bilgisayarda tam olarak gösterilmemektedir ve bu nedenle, üzerinde bulunan aritmetik sonuçları, doğrudan bir temsil ile aynı sonucu vermeyebilir. bilinen "cevap.

Bu

bilgisayar aritmetiği iyi bilinen bir sınırlamadır ve çeşitli yerlerde de tartışılmıştır:

karşılaştırılması skalerler

R bu standart çözüm == kullanmak değil, daha ziyade all.equal fonksiyonu. Veya daha doğrusu, all.equal, isTRUE(all.equal(...)) varsa, farklar hakkında birçok ayrıntı verir.

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

yerine == arasında all.equal kullanmanın

i equals 0.15 

bazı daha fazla örnek verir (son örnekte, bu doğru farklılık gösterecektir göstermek için beklenen).

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 

Biraz daha detay, doğrudan answer to a similar question kopyalanan: karşılaşmış

sorun kayan nokta sık o tam eşleme başarısız bulacaksınız demektir tam olarak çoğu durumda ondalık kesirleri temsil edemezler.R hafifçe yatarken

derken:

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

Sen gerçekten ondalık ne düşündüğünü öğrenebilirsiniz:

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

Bu sayılar farklı, ama temsil edilmemesi olduğunu görebilirsiniz biraz beceriksiz.

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" 

Onların bu sayı Temsil edilebilir en küçük bir fark arasındaki çünkü önemli olan, 2^-53 farklılık olduğunu görebilirsiniz: Biz ikili onlara bakacak olursak (iyi, eşdeğerdir onaltılık) biz daha net bir resim elde değeri 1'e yakın olan iki sayı, olduğu gibi.

Biz bu Temsil edilebilir en küçük sayı R'ın machine alanında bakarak ne olduğunu herhangi bir bilgisayar için öğrenebilirsiniz: Sen oluşturmak için bu gerçeği kullanabilirsiniz

?.Machine 
#.... 
#double.eps  the smallest positive floating-point number x 
#such that 1 + x != 1. It equals base^ulp.digits if either 
#base is 2 or rounding is 0; otherwise, it is 
#(base^ulp.digits)/2. Normally 2.220446e-16. 
#.... 
.Machine$double.eps 
#[1] 2.220446e-16 
sprintf("%a",.Machine$double.eps) 
#[1] "0x1p-52" 

bir fark olduğunu denetler işlevi 'neredeyse eşittir' kayan noktadaki en küçük temsil edilebilir sayıya yakındır. Aslında bu zaten mevcut: 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, ...) 
#.... 

Yani all.equal fonksiyonu aslında sayılar arasındaki farkın iki mantislerdeki arasındaki en küçük farkın karekökü olduğunu kontrol ediyor.

Bu algoritma, denormals adı verilen son derece küçük sayılara yakın biraz eğlenceli olur, ancak bunun için endişelenmenize gerek yoktur.

karşılaştırılması vektörleri

Yukarıdaki tartışma, iki tek değerlerinin karşılaştırılması üstlendi. R'de skaler yoktur, sadece vektörler ve örtük vektörler dilin bir gücüdür. Vektörel elemanların değerini karşılaştırmak için, önceki ilkeler geçerlidir, ancak uygulama biraz farklıdır. == vektörleştirilmiştir (bir eleman-karşılaştırma karşılaştırması) iken all.equal tüm vektörleri tek bir varlık olarak karşılaştırmaktadır.

kullanarak önceki örnekler

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) 

==, öğeye göre yerine

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

iki vektörün üzerinde döngüler bir sürüm gerçekleştirmez "beklenen" sonucu ve all.equal vermez gerekir

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

İşlevsel Bu sürümü sadece ilgili iç görün çoğaltabilir, bunun yerine daha da işlev çağrıları all.equal sarma, Alternatif sadece

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

olarak çağrılabilir

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

yazılabilir, istendiği all.equal.numeric ve örtük vektörelleştirmeyi kullanın: Eğer üzerinde bizim tarafımızdan bu gelebilir (sebebidir) Brian'ın yorumuna ekleme

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

yerine all.equal ing:

# 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 

burada Başına Yuşa uyarı güncellenen kodudur (Teşekkür 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'ın cevabını kısa ve öz bir şekilde açıklayan bağlantıyı kaçırdım. –

+14

'all.equal', farklılıklar olduğunda' FALSE' döndürmez, bu yüzden 'if' ifadesinde kullanırken' isTRUE' ile sarmanız gerekir. –

7

Bu hackish olmakla çabuk:

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