web-dev-qa-db-fra.com

Pourquoi ces chiffres ne 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
253
dplanet

Raison générale (agnostique de la langue)

É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:

Comparer des scalaires

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.

Comparer des vecteurs

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
333
Brian Diggs

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
38
Tyler Rinker

C'est hackish, mais rapide:

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

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().

6
sbha

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
0
Elias EstatisticsEU

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
0
Erdogan CEVHER