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
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
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:
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.
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
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
J'ai raté le lien de Brian qui explique ma réponse succinctement. –
'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'. –
Ceci est hackish, mais rapide:
if(round(i, 10)==0.15) cat("i equals 0.15") else cat("i does not equal 0.15")
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