2017-04-07 4 views
-1

J'essaie de trouver du code Java pour déterminer si deux doubles sont presque égaux. J'ai fait beaucoup de Google et trouvé des morceaux que j'ai rassemblés ici. Là où ça commence à m'échapper c'est l'utilisation d'un "epsilon relatif". Cette approche semble être ce que je cherche. Je ne veux pas avoir à spécifier l'epsilon directement, mais je veux utiliser un epsilon basé sur les amplitudes des deux arguments. Voici le code que j'ai mis ensemble, j'ai besoin d'un contrôle de santé mentale dessus. (Post-scriptum Je sais juste assez de mathématiques pour être dangereux.)Comment déterminer si deux doubles sont presque égaux

public class MathUtils 
{ 
    // http://stackoverflow.com/questions/3728246/what-should-be-the- 
    // epsilon-value-when-performing-double-value-equal-comparison 
    // ULP = Unit in Last Place 
    public static double relativeEpsilon(double a, double b) 
    { 
     return Math.max(Math.ulp(a), Math.ulp(b)); 
    } 

    public static boolean nearlyEqual(double a, double b) 
    { 
     return nearlyEqual(a, b, relativeEpsilon(a, b)); 
    } 

    // http://floating-point-gui.de/errors/comparison/ 
    public static boolean nearlyEqual(double a, double b, double epsilon) 
    { 
     final double absA = Math.abs(a); 
     final double absB = Math.abs(b); 
     final double diff = Math.abs(a - b); 

     if(a == b) 
     { 
      // shortcut, handles infinities 
      return true; 
     } 
     else if(a == 0 || b == 0 || diff < Double.MIN_NORMAL) 
     { 
      // a or b is zero or both are extremely close to it 
      // relative error is less meaningful here 
      // NOT SURE HOW RELATIVE EPSILON WORKS IN THIS CASE 
      return diff < (epsilon * Double.MIN_NORMAL); 
     } 
     else 
     { 
      // use relative error 
      return diff/Math.min((absA + absB), Double.MAX_VALUE) < epsilon; 
     } 
    } 
} 
+1

Un ulp est probablement un peu trop petit d'un epsilon. –

+0

Des recommandations sur comment je devrais le faire? – careysb

+0

Ah, @ Louis Wasserman, ressemble à ce que Klaus a mentionné. – careysb

Répondre

1

Je voudrais utiliser une bibliothèque pour cela, celui que j'utilise normalement est DoubleMath vient bibliothèque Googles goyave. https://google.github.io/guava/releases/19.0/api/docs/com/google/common/math/DoubleMath.html

if (DoubleMath.fuzzyEquals(a, b, epsilon)) { // a and b are equal within the tolerance given } il existe également un fuzzyCompare.

+0

La fuzzyEqals() de la bibliothèque nécessite un paramètre de tolérance que j'essaye d'éviter en utilisant ulp (que je n'ai apparemment pas fait correctement). – careysb

+3

@careysb La raison pour laquelle les bibliothèques demandent une tolérance est que la tolérance correcte ne dépend pas seulement de l'ulp. Cela dépend de la marge d'erreur d'arrondi attendue en tenant compte du calcul jusqu'à présent. –

+0

Exactement, et si l'un des flotteurs/doubles a été créé en utilisant un calcul, vous aurez besoin d'une tolérance car la précision pourrait être perdue. Par exemple '(2f/11 * 9) * 11;' qui est 18.000002, comparé à '3f * 6' qui est 18.0, vous aurez besoin d'une tolérance lors de la comparaison. –

0

La manière habituelle pour comparer 2 valeurs flottantes a,b est:

if (Math.abs(a-b) <= epsilon) do_stuff_if_equal; 
else      do_stuff_if_different; 

Math.abs() est la valeur absolue. Comme je ne code pas en Java, vous devez utiliser la variante double si ce n'est pas le cas. Le epsilon est votre différence. Comme mentionné ulp est trop petit pour cela. Vous devez utiliser une valeur qui a du sens pour les valeurs que vous comparez. Alors, comment calculer epsilon?

C'est un peu difficile et oui, il est possible d'utiliser la magnitude de a,b mais ce n'est pas une manière robuste, car si les exposants de a,b sont trop différents, vous pouvez facilement obtenir des faux positifs. Au lieu de cela, vous devriez utiliser une valeur pleine signification. Par exemple, si vous comparez les coordonnées de position, l'epsilon doit être une fraction de min ou une distance minimale que vous considérez comme le même point. Pour les angles un angle minimal est assez petit comme 1e-6 deg mais la valeur dépend des plages et de la précision avec lesquelles vous travaillez. Pour les plages <-1,1> normalisées, j'utilise habituellement 1e-10 ou 1e-30. Comme vous pouvez le voir, l'epsilon dépend principalement de la précision et de la magnitude de la cible et change très bien d'un cas à l'autre, donc créer une manière uniforme (se débarrasser de epsilon comme vous voulez) n'est pas sûr et seulement causerait des maux de tête plus tard sur.

Pour faciliter cela, je définis généralement une constante _zero ou variable (dans le cas des classes de calcul) qui peuvent être modifiés. Réglez-le sur la valeur par défaut qui est assez bon pour la plupart des cas et si j'ai des problèmes à un moment je sais que je peux facilement le changer ...

Si vous voulez faire votre chemin de toute façon (en ignorant le texte ci-dessus) alors vous peut faire ceci:

if (Math.abs(a)>=Math.abs(b)) epsilon=1e-30*Math.abs(b); 
else       epsilon=1e-30*Math.abs(a); 

mais comme je l'ai dit cela peut conduire à de mauvais résultats. Si vous persistez à utiliser ulp alors j'utiliserais Min au lieu de Max.