web-dev-qa-db-fra.com

Difficulté de comparaison numérique dans R

J'essaie de comparer deux nombres dans R dans le cadre d'une condition if-statement:

(a-b) >= 0.5

Dans ce cas particulier, a = 0,58 et b = 0,08 ... et pourtant (a-b) >= 0.5 Est faux. Je suis conscient des dangers de l'utilisation de == Pour des comparaisons de nombres exacts, et cela semble lié:

(a - b) == 0.5) Est faux, tandis que

all.equal((a - b), 0.5) est vrai.

La seule solution à laquelle je peux penser est d'avoir deux conditions: (a-b) > 0.5 | all.equal((a-b), 0.5). Cela fonctionne, mais est-ce vraiment la seule solution? Dois-je simplement jurer pour toujours de la famille d'opérateurs de comparaison =?

Modifier pour plus de clarté: Je sais que c'est un problème de virgule flottante. Plus fondamentalement, ce que je demande, c'est: que dois-je faire? Quelle est la meilleure façon de traiter des comparaisons supérieures ou égales à dans R, car le >= Ne peut pas vraiment faire confiance?

45
Matt Parker

Je n'ai jamais été fan de all.equal pour de telles choses. Il me semble que la tolérance fonctionne parfois de façon mystérieuse. Pourquoi ne pas simplement vérifier quelque chose de plus qu'une tolérance inférieure à 0,05

tol = 1e-5

(a-b) >= (0.05-tol)

En général, sans arrondir et avec juste une logique conventionnelle, je trouve la logique droite meilleure que toutes.

Si x == y puis x-y == 0. Peut-être x-y n'est pas exactement 0 donc pour de tels cas j'utilise

abs(x-y) <= tol

Vous devez quand même définir une tolérance pour all.equal et c'est plus compact et plus simple que all.equal.

41
John

Vous pouvez créer ceci en tant qu'opérateur séparé ou écraser la fonction> = d'origine (probablement pas une bonne idée) si vous souhaitez utiliser cette approche fréquemment:

# using a tolerance
epsilon <- 1e-10 # set this as a global setting
`%>=%` <- function(x, y) (x + epsilon > y)

# as a new operator with the original approach
`%>=%` <- function(x, y) (all.equal(x, y)==TRUE | (x > y))

# overwriting R's version (not advised)
`>=` <- function(x, y) (isTRUE(all.equal(x, y)) | (x > y))

> (a-b) >= 0.5
[1] TRUE
> c(1,3,5) >= 2:4
[1] FALSE FALSE  TRUE
14
Shane

Par souci d'exhaustivité, je soulignerai que, dans certaines situations, vous pouvez simplement arrondir à quelques décimales (et c'est une sorte de solution boiteuse par rapport à la meilleure solution précédemment publiée.)

round(0.58 - 0.08, 2) == 0.5
11
icio

Choisissez un certain niveau de tolérance:

epsilon <- 1e-10

Ensuite, utilisez

(a-b+epsilon) >= 0.5
5
Rob Hyndman

Encore un commentaire. Le all.equal est un générique. Pour les valeurs numériques, il utilise all.equal.numeric. Une inspection de cette fonction montre qu'elle a utilisé .Machine$double.eps^0.5, où .Machine$double.eps est défini comme

double.eps: the smallest positive floating-point number ‘x’ such that
          ‘1 + x != 1’.  It equals ‘double.base ^ ulp.digits’ if either
          ‘double.base’ is 2 or ‘double.rounding’ is 0; otherwise, it
          is ‘(double.base ^ double.ulp.digits) / 2’.  Normally
          ‘2.220446e-16’.

(Page de manuel .Machine).

En d'autres termes, ce serait un choix acceptable pour votre tolérance:

myeq <- function(a, b, tol=.Machine$double.eps^0.5)
      abs(a - b) <= tol
5
January

Mais, si vous utilisez des tolérances de toute façon, pourquoi vous souciez-vous que a-b == .5 (en fait) ne soit pas évalué? Si vous utilisez des tolérances de toute façon, vous dites que je ne me soucie pas exactement des points finaux.

Voici ce qui est vrai si ((a-b)> = .5) if ((a-b) <.5)

l'un de ceux-ci devrait toujours évaluer vrai sur chaque paire de doubles. Tout code qui utilise l'un définit implicitement au moins une opération non sur l'autre. Si vous utilisez des tolérances pour obtenir un réel .5 inclus dans le premier mais que votre problème est défini sur un domaine continu, vous n'accomplissez pas grand-chose. Dans la plupart des problèmes impliquant des valeurs continues dans le problème sous-jacent, cela ne servira à rien, car les valeurs arbitrairement supérieures à 0,5 seront toujours évaluées comme elles le devraient. Des valeurs arbitrairement proches de 0,5 iront au "mauvais" contrôle de flux, mais dans des problèmes continus où vous utilisez une précision appropriée, cela n'a pas d'importance.

Le seul moment où les tolérances ont un sens est lorsque vous avez affaire à des problèmes de type if ((a-b) == c) if ((a-b)! = C)

Ici, aucune "précision appropriée" ne peut vous aider. La raison en est que vous devez être prêt à ce que le second soit toujours évalué à vrai, sauf si vous définissez les bits de a-b à un niveau très bas à la main, alors qu'en fait vous voulez probablement que le premier soit parfois vrai.

3
Jer

<= et >= les comparaisons ne sont pas spécifiques au langage lorsque la difficulté numérique est augmentée dans les nombres à virgule flottante.

IsSmallerOrEqual <- function(a,b) {   # To check a <= b
# Check whether "Mean relative difference..." exist in all.equal's result; 
# 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; To check |-2-(-2.2)| <= 0.2
IsSmallerOrEqual(abs(-2-(-2.2)), 0.3) # TRUE
IsSmallerOrEqual(abs(-2-(-2.2)), 0.1) # FALSE

IsBiggerOrEqual  <- function(a,b) {   # To check a >= b
# Check whether "Mean relative difference..." exist in all.equal's result; 
# 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) # TRUE
IsBiggerOrEqual(4,3) # TRUE
IsBiggerOrEqual(3,4) # FALSE
IsBiggerOrEqual(0.58 - 0.08,0.5)  # TRUE

Si all.equal n'est pas traité, nous pouvons rencontrer des erreurs.

Ce qui suit n'est pas nécessaire mais utile:

abs(-2-(-2.2)) # 0.2

sprintf("%.54f",abs(-2-(-2.2)))  # "0.200000000000000177635683940025046467781066894531250000"
sprintf("%.54f",0.2)             # "0.200000000000000011102230246251565404236316680908203125"

all.equal(abs(-2-(-2.2)), 0.2)  # TRUE; check nearly equivalence of floating point numbers
identical(abs(-2-(-2.2)), 0.2)  # FALSE; check exact equivalence of floating point numbers
2
Erdogan CEVHER