Pourquoi ces chiffres ne sont-ils pas égaux?

273

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
dplanet
la source
7
Voir également stackoverflow.com/q/6874867 et stackoverflow.com/q/2769510 . Le R Inferno est également une autre excellente lecture.
Aaron a quitté Stack Overflow
1
Questions et réponses indépendantes de la langue à l'échelle du site: les mathématiques en virgule flottante sont-elles rompues?
Gregor Thomas
dplanet, j'ai ajouté une solution pour tous les cas de comparaison ("<=", "> =", "=") en arithmétique double précision ci-dessous. J'espère que ça aide.
Erdogan CEVHER

Réponses:

355

Raison générale (indépendante de la langue)

Étant donné que tous les nombres ne peuvent pas être représentés exactement dans l' arithmétique à virgule flottante IEEE (la norme que presque tous les ordinateurs utilisent pour représenter les nombres décimaux et faire des calculs avec eux), vous n'obtiendrez pas toujours ce que vous attendiez. Cela est particulièrement vrai car certaines valeurs qui sont des décimales simples et finies (telles que 0,1 et 0,05) ne sont pas représentées exactement dans l'ordinateur et les résultats de l'arithmétique peuvent donc ne pas donner un résultat identique à une représentation directe du " réponse connue.

Il s'agit d'une limitation bien connue de l'arithmétique informatique et est discutée à plusieurs endroits:

Comparaison des scalaires

La solution standard à cela Rn'est pas d'utiliser ==, mais plutôt la all.equalfonction. Ou plutôt, puisque all.equaldonne 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 autres exemples d'utilisation all.equalau 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

Quelques détails supplémentaires, directement copiés d'une réponse à une question similaire :

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

tandis que R se trouve légèrement lorsque vous dites:

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

Vous pouvez découvrir 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 que ces chiffres sont différents, mais la représentation est un peu lourde. Si nous les regardons en binaire (enfin, 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 car ce nombre est la plus petite différence représentable entre deux nombres dont la valeur est proche de 1, comme c'est le cas.

Nous pouvons découvrir pour n'importe quel ordinateur donné ce que ce plus petit nombre représentable est en regardant dans le champ machine de R :

 ?.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 «presque égale» 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, ...)
#....

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

Cet algorithme devient un peu drôle près de très petits nombres appelés dénormals, mais vous n'avez pas à vous en soucier.

Comparaison des vecteurs

La discussion ci-dessus suppose une comparaison de deux valeurs uniques. Dans 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 élément par élément) tandis que all.equalcompare 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 résultat "attendu" et all.equalne fonctionne pas par élément

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

Au contraire, une version qui fait une boucle sur les deux vecteurs doit être utilisée

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

Si une version fonctionnelle de celle-ci est souhaitée, elle peut être écrite

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

qui peut être appelé juste

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

Alternativement, au lieu d'encapsuler all.equalencore plus d'appels de fonction, vous pouvez simplement répliquer les internes pertinents all.equal.numericet utiliser la 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

Telle est l’approche adoptée par dplyr::near, qui se

C'est un moyen sûr de comparer si deux vecteurs de nombres à virgule flottante sont (par paire) égaux. C'est plus sûr que d'utiliser ==, car il a une tolérance intégrée

dplyr::near(a, b)
#[1]  TRUE  TRUE  TRUE FALSE
Brian Diggs
la source
R est un environnement logiciel libre pour le calcul statistique ??
kittygirl
41

Ajout au commentaire de Brian (qui est la raison), vous pouvez surmonter cela en utilisant à la all.equalplace:

# 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 l'avertissement de Joshua, voici le code mis à jour (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
Tyler Rinker
la source
17
all.equalne revient pas FALSEquand il y a des différences, vous devez donc envelopper avec isTRUElors de son utilisation dans une ifinstruction.
Joshua Ulrich
12

C'est hackish, mais rapide:

if(round(i, 10)==0.15) cat("i equals 0.15") else cat("i does not equal 0.15")
Hillary Sanders
la source
2
Mais vous pouvez utiliser le all.equal(... tolerance)paramètre. all.equal(0.147, 0.15, tolerance=0.05)est vrai.
smci
10

dplyr::near()est une option pour tester si deux vecteurs de nombres à virgule flottante sont égaux. Voici l'exemple de la documentation :

sqrt(2) ^ 2 == 2
#> [1] FALSE
library(dplyr)
near(sqrt(2) ^ 2, 2)
#> [1] TRUE

La fonction a un paramètre de tolérance intégré: tol = .Machine$double.eps^0.5qui peut être ajusté. Le paramètre par défaut est le même que celui par défaut pour all.equal().

sbha
la source
0

J'avais un problème similaire. J'ai utilisé la solution suivante.

@ J'ai trouvé cette solution de contournement concernant les intervalles de coupe inégaux. @ J'ai utilisé la fonction ronde en R. En définissant l'option sur 2 chiffres, je n'ai pas résolu le problème.

options(digits = 2)
cbind(
  seq(      from = 1, to = 9, by = 1 ), 
  cut( seq( from = 1, to = 9, by = 1),          c( 0, 3, 6, 9 ) ),
  seq(      from = 0.1, to = 0.9, by = 0.1 ), 
  cut( seq( from = 0.1, to = 0.9, by = 0.1),    c( 0, 0.3, 0.6, 0.9 )),
  seq(      from = 0.01, to = 0.09, by = 0.01 ), 
  cut( seq( from = 0.01, to = 0.09, by = 0.01),    c( 0, 0.03, 0.06, 0.09 ))
)

sortie d'intervalles de coupe inégaux en fonction des options (chiffres = 2):

  [,1] [,2] [,3] [,4] [,5] [,6]
 [1,]    1    1  0.1    1 0.01    1
 [2,]    2    1  0.2    1 0.02    1
 [3,]    3    1  0.3    2 0.03    1
 [4,]    4    2  0.4    2 0.04    2
 [5,]    5    2  0.5    2 0.05    2
 [6,]    6    2  0.6    2 0.06    3
 [7,]    7    3  0.7    3 0.07    3
 [8,]    8    3  0.8    3 0.08    3
 [9,]    9    3  0.9    3 0.09    3


options(digits = 200)
cbind(
  seq(      from = 1, to = 9, by = 1 ), 
  cut( round(seq( from = 1, to = 9, by = 1), 2),          c( 0, 3, 6, 9 ) ),
  seq(      from = 0.1, to = 0.9, by = 0.1 ), 
  cut( round(seq( from = 0.1, to = 0.9, by = 0.1), 2),    c( 0, 0.3, 0.6, 0.9 )),
  seq(      from = 0.01, to = 0.09, by = 0.01 ), 
  cut( round(seq( from = 0.01, to = 0.09, by = 0.01), 2),    c( 0, 0.03, 0.06, 0.09 ))
)

sortie d'intervalles de coupe égaux basés sur la fonction ronde:

      [,1] [,2] [,3] [,4] [,5] [,6]
 [1,]    1    1  0.1    1 0.01    1
 [2,]    2    1  0.2    1 0.02    1
 [3,]    3    1  0.3    1 0.03    1
 [4,]    4    2  0.4    2 0.04    2
 [5,]    5    2  0.5    2 0.05    2
 [6,]    6    2  0.6    2 0.06    2
 [7,]    7    3  0.7    3 0.07    3
 [8,]    8    3  0.8    3 0.08    3
 [9,]    9    3  0.9    3 0.09    3
Elias EstatisticsEU
la source
0

Comparaisons généralisées ("<=", "> =", "=") en arithmétique à double précion:

Comparaison de a <= b:

IsSmallerOrEqual <- function(a,b) {   
# Control the existence of "Mean relative difference..." in all.equal; 
# if exists, it results in character, not logical:
if (   class(all.equal(a, b)) == "logical" && (a<b | all.equal(a, b))) { return(TRUE)
 } else if (a < b) { return(TRUE)
     } else { return(FALSE) }
}

IsSmallerOrEqual(abs(-2-(-2.2)), 0.2) # TRUE
IsSmallerOrEqual(abs(-2-(-2.2)), 0.3) # TRUE
IsSmallerOrEqual(abs(-2-(-2.2)), 0.1) # FALSE
IsSmallerOrEqual(3,3); IsSmallerOrEqual(3,4); IsSmallerOrEqual(4,3) 
# TRUE; TRUE; FALSE

Comparaison de a> = b:

IsBiggerOrEqual <- function(a,b) {
# Control the existence of "Mean relative difference..." in all.equal; 
# if exists, it results in character, not logical:
if (   class(all.equal(a, b)) == "logical" && (a>b | all.equal(a, b))) { return(TRUE)
 } else if (a > b) { return(TRUE)
     } else { return(FALSE) }
}
IsBiggerOrEqual(3,3); IsBiggerOrEqual(4,3); IsBiggerOrEqual(3,4) 
# TRUE; TRUE; FALSE

Comparaison de a = b:

IsEqual <- function(a,b) {
# Control the existence of "Mean relative difference..." in all.equal; 
# if exists, it results in character, not logical:
if (   class(all.equal(a, b)) == "logical" ) { return(TRUE)
 } else { return(FALSE) }
}

IsEqual(0.1+0.05,0.15) # TRUE
Erdogan CEVHER
la source