Quelle est la manière correcte / standard de vérifier si la différence est inférieure à la précision de la machine?

36

Je me retrouve souvent dans des situations où il est nécessaire de vérifier si la différence obtenue est supérieure à la précision de la machine. On dirait à cette fin R a une variable à portée de main: .Machine$double.eps. Cependant, lorsque je me tourne vers le code source R pour obtenir des instructions sur l'utilisation de cette valeur, je vois plusieurs modèles différents.

Exemples

Voici quelques exemples tirés de la statsbibliothèque:

t.test.R

if(stderr < 10 *.Machine$double.eps * abs(mx))

chisq.test.R

if(abs(sum(p)-1) > sqrt(.Machine$double.eps))

integrer.R

rel.tol < max(50*.Machine$double.eps, 0.5e-28)

lm.influence.R

e[abs(e) < 100 * .Machine$double.eps * median(abs(e))] <- 0

princomp.R

if (any(ev[neg] < - 9 * .Machine$double.eps * ev[1L]))

etc.

Des questions

  1. Comment peut - on comprendre le raisonnement derrière tous ces différents 10 *, 100 *, 50 *et sqrt()modificateurs?
  2. Existe-t-il des directives sur l'utilisation .Machine$double.epspour ajuster les différences en raison de problèmes de précision?
Karolis Koncevičius
la source
6
Ainsi, les deux postes concluent que "le degré raisonnable de certitude" dépend de votre candidature. Comme étude de cas, vous pouvez consulter ce post sur R-devel ; "Aha! 100 fois la précision de la machine, pas tant que ça quand les chiffres eux-mêmes sont à deux chiffres." (Peter Dalgaard, membre de l'équipe R Core)
Henrik
1
@ KarolisKoncevičius, je ne pense pas que ce soit aussi simple que cela. Cela a à voir avec les erreurs générales présentes dans les mathématiques en virgule flottante et le nombre d'opérations que vous exécutez sur elles. Si vous comparez simplement à des nombres à virgule flottante, utilisez double.eps. Si vous effectuez plusieurs opérations sur un nombre à virgule flottante, votre tolérance aux erreurs doit également s'ajuster. C'est pourquoi all.equal vous donne un toleranceargument.
Joseph Wood
1
Jetez également un œil à la mise en œuvre de la fonctionnalité nextafter dans R ce qui vous donnera le prochain nombre double plus grand.
GKi

Réponses:

4

La précision de la machine doubledépend de sa valeur actuelle. .Machine$double.epsdonne la précision lorsque les valeurs sont 1. Vous pouvez utiliser la fonction C nextAfterpour obtenir la précision de la machine pour d'autres valeurs.

library(Rcpp)
cppFunction("double getPrec(double x) {
  return nextafter(x, std::numeric_limits<double>::infinity()) - x;}")

(pr <- getPrec(1))
#[1] 2.220446e-16
1 + pr == 1
#[1] FALSE
1 + pr/2 == 1
#[1] TRUE
1 + (pr/2 + getPrec(pr/2)) == 1
#[1] FALSE
1 + pr/2 + pr/2 == 1
#[1] TRUE
pr/2 + pr/2 + 1 == 1
#[1] FALSE

La valeur ajoutée aà la valeur bne changera pas bquand aest la <= moitié de la précision de la machine. Vérifier si la différence est plus petite que la précision de la machine <. Les modificateurs peuvent prendre en compte les cas typiques de la fréquence à laquelle un ajout n'a pas montré de changement.

Dans R, la précision de la machine peut être estimée avec:

getPrecR <- function(x) {
  y <- log2(pmax(.Machine$double.xmin, abs(x)))
  ifelse(x < 0 & floor(y) == y, 2^(y-1), 2^floor(y)) * .Machine$double.eps
}
getPrecR(1)
#[1] 2.220446e-16

Chaque doublevaleur représente une plage. Pour un ajout simple, la plage du résultat dépend de la réanimation de chaque somme et également de la plage de leur somme.

library(Rcpp)
cppFunction("std::vector<double> getRange(double x) {return std::vector<double>{
   (nextafter(x, -std::numeric_limits<double>::infinity()) - x)/2.
 , (nextafter(x, std::numeric_limits<double>::infinity()) - x)/2.};}")

x <- 2^54 - 2
getRange(x)
#[1] -1  1
y <- 4.1
getRange(y)
#[1] -4.440892e-16  4.440892e-16
z <- x + y
getRange(z)
#[1] -2  2
z - x - y #Should be 0
#[1] 1.9

2^54 - 2.9 + 4.1 - (2^54 + 5.9) #Should be -4.7
#[1] 0
2^54 - 2.9 == 2^54 - 2      #Gain 0.9
2^54 - 2 + 4.1 == 2^54 + 4  #Gain 1.9
2^54 + 5.9 == 2^54 + 4      #Gain 1.9

Pour une plus grande précision, on Rmpfrpourrait utiliser.

library(Rmpfr)
mpfr("2", 1024L)^54 - 2.9 + 4.1 - (mpfr("2", 1024L)^54 + 5.9)
#[1] -4.700000000000000621724893790087662637233734130859375

Au cas où il pourrait être converti en entier gmppourrait être utilisé (ce qui est dans Rmpfr).

library(gmp)
as.bigz("2")^54 * 10 - 29 + 41 - (as.bigz("2")^54 * 10 + 59)
#[1] -47
GKi
la source
Merci beaucoup. Je pense que c'est une bien meilleure réponse. Cela illustre bien de nombreux points. La seule chose qui est encore un peu floue pour moi est - peut-on trouver les modificateurs (comme * 9, etc.) par lui-même? Et si oui, comment ...
Karolis Koncevičius
Je pense que ce modificateur est comme le niveau de signification dans les statistiques et augmentera du nombre d'opérations que vous avez effectuées en combinaison avec le risque choisi pour rejeter une comparaison correcte.
GKi
3

Définition d'une machine.eps: c'est la valeur la plus basse  eps pour laquelle  1+eps n'est pas 1

En règle générale (en supposant une représentation en virgule flottante avec la base 2):
Cela epsfait la différence pour la plage 1 .. 2,
pour la plage 2 .. 4 la précision est 2*eps
et ainsi de suite.

Malheureusement, il n'y a pas de bonne règle empirique ici. Il est entièrement déterminé par les besoins de votre programme.

Dans R, nous avons all.equal comme méthode intégrée pour tester l'égalité approximative. Vous pourriez donc utiliser quelque chose comme (x<y) | all.equal(x,y)

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

Google Mock dispose d'un certain nombre de comparateurs à virgule flottante pour les comparaisons à double précision, y compris DoubleEqet DoubleNear. Vous pouvez les utiliser dans un matcher de tableau comme celui-ci:

ASSERT_THAT(vec, ElementsAre(DoubleEq(0.1), DoubleEq(0.2)));

Mise à jour:

Les recettes numériques fournissent une dérivation pour démontrer que l'utilisation d'un quotient de différence unilatérale sqrtest un bon choix de taille de pas pour les approximations de différences finies de dérivés.

Le site d'articles Wikipédia Recettes numériques, 3e édition, section 5.7, qui se trouve aux pages 229-230 (un nombre limité de pages vues est disponible sur http://www.nrbook.com/empanel/ ).

all.equal(target, current,
           tolerance = .Machine$double.eps ^ 0.5, scale = NULL,
           ..., check.attributes = TRUE)

Cette arithmétique à virgule flottante IEEE est une limitation bien connue de l'arithmétique informatique et est discutée à plusieurs endroits:

. dplyr::near()est une autre option pour tester si deux vecteurs de nombres à virgule flottante sont égaux.

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

Sreeram Nair
la source
2
Merci pour la réponse. Pour le moment, je pense que c'est trop minime pour être une réponse acceptée. Il ne semble pas répondre aux deux principales questions de la poste. Par exemple, il indique "il est déterminé par les besoins de votre programme". Ce serait bien de montrer un ou deux exemples de cette déclaration - peut-être un petit programme et comment la tolérance peut être déterminée par lui. Peut-être en utilisant l'un des scripts R mentionnés. Il y all.equal()a aussi sa propre hypothèse comme tolérance par défaut sqrt(double.eps)- pourquoi est-ce la valeur par défaut? Est-ce une bonne règle à utiliser sqrt()?
Karolis Koncevičius
Voici le code que R utilise pour calculer eps (extrait dans son propre programme). J'ai également mis à jour la réponse avec de nombreux points de discussion que j'avais déjà examinés auparavant. J'espère que la même chose vous aidera à mieux comprendre.
Sreeram Nair
Un sincère +1 pour tout l'effort. Mais dans l'état actuel, je ne peux toujours pas accepter la réponse. Cela semble un peu dépassé avec beaucoup de références, mais en termes de réponse réelle aux 2 questions postées: 1) comment comprendre les modificateurs 100x, 50x, etc. dans la stats::source R , et 2) quelles sont les lignes directrices; la réponse est assez mince. La seule phrase applicable semble être la référence de "Recettes numériques" à propos de sqrt () étant un bon défaut, ce qui est vraiment pertinent, je pense. Ou peut-être que je manque quelque chose ici.
Karolis Koncevičius