2012-02-29 5 views
197

Le code suivant est évidemment faux. Quel est le problème?Pourquoi ces chiffres ne sont-ils pas égaux?

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

Voir aussi http: //stackoverflow.com/q/6874867 et http://stackoverflow.com/q/2769510. Le [R Inferno] (http://www.burns-stat.com/pages/Tutor/R_inferno.pdf) est aussi une autre excellente lecture. – Aaron

Répondre

261

(langue agnostique) raison

Comme tous les chiffres peuvent être représentés exactement IEEE floating point arithmetic (la norme que presque tous les ordinateurs utilisent pour représenter des nombres décimaux et faire des mathématiques avec eux), vous ne serez pas toujours Obtenez ce que vous attendiez. Cela est particulièrement vrai parce que certaines valeurs qui sont simples, décimales finies (telles que 0,1 et 0,05) ne sont pas représentées exactement dans l'ordinateur et donc les résultats de l'arithmétique sur eux ne peuvent pas donner un résultat qui est identique à une représentation directe du connue "réponse.

C'est une limitation bien connue de l'arithmétique informatique et est discuté en plusieurs endroits:

Comparaison de

scalaires

La solution standard pour cela dans R est de ne pas utiliser ==, mais plutôt la fonction all.equal. Ou plutôt, puisque all.equal donne beaucoup de détails sur les différences s'il y en a, isTRUE(all.equal(...)).

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

cède

i equals 0.15 

Quelques exemples d'utilisation all.equal au lieu de == (le dernier exemple est censé montrer que cela montrera correctement les différences).

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 

Un peu plus en détail, directement copiés à partir d'un answer to a similar question:

Le problème que vous avez rencontré est que la virgule flottante ne peut pas représenter les fractions décimales exactement dans la plupart des cas, ce qui signifie que vous trouverez souvent que les correspondances exactes échouent.

tandis que R est un peu quand vous dites:

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

Vous pouvez savoir ce qu'il pense vraiment en décimal:

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

Vous pouvez voir ces chiffres sont différents, mais la représentation est un peu lourd. Si nous regardons en binaire (bien, hexadécimal, ce qui est équivalent), nous obtenons une image plus claire:

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" 

Vous pouvez voir qu'ils diffèrent par 2^-53, ce qui est important parce que ce nombre est la différence représentable le plus faible entre deux nombres dont la valeur est proche de 1, comme cela est.

Nous pouvons trouver pour tout nombre représentable ordinateur ce que ce petit donné est en regardant dans le domaine de R machine:

?.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" 

Vous pouvez utiliser ce fait pour créer une fonction « égale presque », qui vérifie que la différence est proche du plus petit nombre représentable en virgule flottante. En fait, cela existe déjà: 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, ...) 
#.... 

donc la fonction all.equal vérifie en fait que la différence entre les chiffres est la racine carrée de la plus petite différence entre deux mantisses.

Cet algorithme devient un peu drôle à proximité de nombres extrêmement petits appelés dénormaux, mais vous n'avez pas à vous en préoccuper.

vecteurs Comparer

La discussion ci-dessus suppose une comparaison de deux valeurs uniques. En R, il n'y a pas de scalaires, juste des vecteurs et la vectorisation implicite est une force du langage. Pour comparer la valeur des vecteurs par élément, les principes précédents sont valables, mais la mise en œuvre est légèrement différente. == est vectorisé (fait une comparaison par élément) tandis que all.equal compare les vecteurs entiers comme une seule entité.

Utilisation des exemples précédents

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) 

== ne donne pas le « attendu » résultat et all.equal ne pas effectuer élément par élément

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

plutôt une version qui passe en boucle sur les deux vecteurs doivent être utilisé

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

Si fonctionnel version de cela est souhaité, il peut être écrit

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

qui peut être appelé comme juste

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

Au lieu d'envelopper all.equal dans les appels de fonction encore plus, vous pouvez simplement reproduire les éléments internes pertinents all.equal.numeric et utiliser vectorisation implicite:

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

Ajout au commentaire de Brian (ce qui est la raison), vous pouvez venir sur ce que nous ing all.equal à la place:

# 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 

l'avertissement de Per Joshua est le code mis à jour ici (Merci 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

J'ai raté le lien de Brian qui explique ma réponse succinctement. –

+14

'all.equal' ne retourne pas' FALSE' quand il y a des différences, donc vous devez l'enrouler avec 'isTRUE' quand vous l'utilisez dans une instruction' if'. –

7

Ceci est hackish, mais rapide:

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