295 votes

Pourquoi ces nombres sont-ils pas égaux?

Le code suivant est évidemment faux. Quel est le problème?

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

392voto

Brian Diggs Points 22433

Générale de la langue (agnostique) raison

Depuis pas tous les numéros peuvent être représentés exactement à virgule flottante IEEE de l'arithmétique (la norme de presque tous les ordinateurs utilisent pour représenter les nombres décimaux et de faire des maths avec eux), vous ne serez pas toujours obtenir ce que vous avez prévu. Cela est particulièrement vrai parce que certaines valeurs qui sont simples, fini de décimales (tels que 0,1 et 0,05) ne sont pas représentés exactement dans l'ordinateur, et donc les résultats de l'arithmétique ne peuvent donner un résultat identique à une représentation directe de la "connu" réponse.

C'est une limitation bien connue de l'arithmétique des ordinateurs et est discutée dans plusieurs endroits:

En comparant les scalaires

La solution standard pour ce en R est de ne pas utiliser ==, mais plutôt l' all.equal fonction. Ou plutôt, depuis 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")

les rendements

i equals 0.15

Quelques exemples de l'utilisation de all.equal au lieu de == (le dernier exemple est censé montrer que cela permettra d'afficher 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ées à partir d'une réponse à une question similaire:

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

alors que R est légèrement lorsque vous dites:

> 1.1-0.2
[1] 0.9
> 0.9
[1] 0.9

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

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

Vous pouvez voir ces numéros sont différents, mais la représentation est un peu lourd. Si nous les regardons en binaire (bien, hex, 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 représentable le plus faible de la différence entre deux nombres dont la valeur est proche de 1, comme c'est.

Nous pouvons trouver pour un ordinateur donné que ce plus petit nombre représentable est en regardant dans la R du champ 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 "presque égal à' la fonction qui vérifie que la différence est proche de représentable le plus faible nombre 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 le tout.l'égalité de la fonction est en fait de vérifier que la différence entre les nombres est la racine carrée de la plus petite différence entre deux mantissas.

Cet algorithme va un peu drôle à proximité de très petits nombres appelés denormals, mais vous n'avez pas besoin de s'inquiéter à ce sujet.

La comparaison de vecteurs

La discussion ci-dessus suppose une comparaison de deux valeurs. Dans R, il n'y a pas des scalaires, juste des vecteurs et implicites de la vectorisation est une force de la langue. Pour une comparaison de la valeur des vecteurs de l'élément de sage, les principes précédents, mais la mise en œuvre est légèrement différente. == est vectorisé (un élément-sage de comparaison), tandis que all.equal compare l'ensemble des vecteurs comme une entité unique.

En utilisant les 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 "prévu" et all.equal n'effectuent pas l'élément-sage

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

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

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

Si une version fonctionnelle de ce qui est désiré, il peut être écrite

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

45voto

Tyler Rinker Points 27607

Ajoutant à Brian le commentaire (ce qui est la raison), vous pouvez venir en utilisant 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

Par Joshua avertissement voici le code mis à jour (Merci Josué):

 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

13voto

Hillary Sanders Points 450

C'est hackish, mais rapide:

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X