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
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
Réponses:
É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:
La solution standard à cela R
n'est pas d'utiliser ==
, mais plutôt la all.equal
fonction. 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")
les rendements
i equals 0.15
Quelques autres 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
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.
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.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 résultat "attendu" et all.equal
ne 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.equal
encore plus d'appels de fonction, vous pouvez simplement répliquer les internes pertinents all.equal.numeric
et 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
Ajout au commentaire de Brian (qui est la raison), vous pouvez surmonter cela en utilisant à la all.equal
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 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
all.equal
ne revient pas FALSE
quand il y a des différences, vous devez donc envelopper avec isTRUE
lors de son utilisation dans une if
instruction.
C'est hackish, mais rapide:
if(round(i, 10)==0.15) cat("i equals 0.15") else cat("i does not equal 0.15")
all.equal(... tolerance)
paramètre. all.equal(0.147, 0.15, tolerance=0.05)
est vrai.
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.5
qui peut être ajusté. Le paramètre par défaut est le même que celui par défaut pour all.equal()
.
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
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