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
Étant donné que tous les nombres ne peuvent pas être représentés exactement dans arithmétique à virgule flottante IEEE (la norme utilisée par presque tous les ordinateurs pour représenter les nombres décimaux et en faire le calcul avec eux), vous n'obtiendrez pas toujours ce que vous espériez. Ceci est d'autant plus vrai que certaines valeurs qui sont simples et décimales finies (telles que 0.1 et 0.05) ne sont pas représentées exactement dans l'ordinateur et que les résultats de l'arithmétique sur ces valeurs peuvent ne pas donner un résultat identique à une représentation directe de " connu "réponse.
Ceci est une limitation bien connue de l'arithmétique informatique et est discuté à plusieurs endroits:
La solution standard à ceci dans R
n'est pas d'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")
les rendements
i equals 0.15
Quelques exemples supplémentaires d'utilisation de all.equal
À la place 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'un réponse à une question similaire :
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 constaterez souvent que les correspondances exactes échouent.
tandis que R ment légèrement quand vous dites:
1.1-0.2
#[1] 0.9
0.9
#[1] 0.9
Vous pouvez trouver 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 difficile à manier. 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 de 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, en l'occurrence.
Nous pouvons découvrir quel ordinateur est ce plus petit nombre représentable en cherchant 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, ...)
#....
La fonction all.equal vérifie donc que la différence entre les nombres est la racine carrée de la plus petite différence entre deux mantisses.
Cet algorithme va un peu drôle près de très petits nombres appelés dénormaux, mais vous n'avez pas à vous en préoccuper.
La discussion ci-dessus supposait une comparaison de deux valeurs simples. En R, il n'y a pas de scalaires, mais des vecteurs et la vectorisation implicite est une force du langage. Pour comparer la valeur des vecteurs élément par élément, les principes précédents sont valables, mais la mise en œuvre est légèrement différente. ==
Est vectorisé (effectue une comparaison élément par élément) tandis que all.equal
Compare l'ensemble des vecteurs en tant qu'entité unique.
Utiliser 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 résultat "attendu" et all.equal
Ne fonctionne pas élément 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 lieu de cela, une version qui 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 ceci est désirée, elle 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
Sinon, au lieu d'encapsuler all.equal
Dans encore plus d'appels de fonction, vous pouvez simplement répliquer les éléments internes pertinents de 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
C’est l’approche adoptée par dplyr::near
, Qui se documente comme
C'est un moyen sûr de comparer si deux vecteurs de nombres à virgule flottante sont égaux (deux à deux). Ceci 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
En ajoutant au commentaire de Brian (qui en est la raison), vous pouvez résoudre ce problème en utilisant all.equal
au lieu:
# 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 Joshua est 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
C'est hackish, mais rapide:
if(round(i, 10)==0.15) cat("i equals 0.15") else cat("i does not equal 0.15")
dplyr::near()
est une option permettant de vérifier si deux vecteurs de nombres en virgule flottante sont égaux. Voici l'exemple de docs :
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 identique à 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 à propos d'intervalles de coupe inégaux. @ J'ai utilisé la fonction round dans R. En réglant l'option sur 2 chiffres, le problème n'a pas été résolu.
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 round:
[,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 en double précision:
en comparant 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
en comparant 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
en comparant 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