it-swarm.com.de

Java doppelter Vergleich epsilon

Ich habe eine Klasse geschrieben, die auf Gleichheit prüft, kleiner als und größer als bei zwei Double in Java. Mein allgemeiner Fall ist der Vergleich von Preisen, die eine Genauigkeit von einem halben Cent haben können. 59,005 gegenüber 59,395. Ist das von mir gewählte Epsilon für diese Fälle geeignet?

private final static double EPSILON = 0.00001;


/**
 * Returns true if two doubles are considered equal.  Tests if the absolute
 * difference between two doubles has a difference less then .00001.   This
 * should be fine when comparing prices, because prices have a precision of
 * .001.
 *
 * @param a double to compare.
 * @param b double to compare.
 * @return true true if two doubles are considered equal.
 */
public static boolean equals(double a, double b){
    return a == b ? true : Math.abs(a - b) < EPSILON;
}


/**
 * Returns true if two doubles are considered equal. Tests if the absolute
 * difference between the two doubles has a difference less then a given
 * double (epsilon). Determining the given epsilon is highly dependant on the
 * precision of the doubles that are being compared.
 *
 * @param a double to compare.
 * @param b double to compare
 * @param epsilon double which is compared to the absolute difference of two
 * doubles to determine if they are equal.
 * @return true if a is considered equal to b.
 */
public static boolean equals(double a, double b, double epsilon){
    return a == b ? true : Math.abs(a - b) < epsilon;
}


/**
 * Returns true if the first double is considered greater than the second
 * double.  Test if the difference of first minus second is greater then
 * .00001.  This should be fine when comparing prices, because prices have a
 * precision of .001.
 *
 * @param a first double
 * @param b second double
 * @return true if the first double is considered greater than the second
 *              double
 */
public static boolean greaterThan(double a, double b){
    return greaterThan(a, b, EPSILON);
}


/**
 * Returns true if the first double is considered greater than the second
 * double.  Test if the difference of first minus second is greater then
 * a given double (epsilon).  Determining the given epsilon is highly
 * dependant on the precision of the doubles that are being compared.
 *
 * @param a first double
 * @param b second double
 * @return true if the first double is considered greater than the second
 *              double
 */
public static boolean greaterThan(double a, double b, double epsilon){
    return a - b > epsilon;
}


/**
 * Returns true if the first double is considered less than the second
 * double.  Test if the difference of second minus first is greater then
 * .00001.  This should be fine when comparing prices, because prices have a
 * precision of .001.
 *
 * @param a first double
 * @param b second double
 * @return true if the first double is considered less than the second
 *              double
 */
public static boolean lessThan(double a, double b){
    return lessThan(a, b, EPSILON);
}


/**
 * Returns true if the first double is considered less than the second
 * double.  Test if the difference of second minus first is greater then
 * a given double (epsilon).  Determining the given epsilon is highly
 * dependant on the precision of the doubles that are being compared.
 *
 * @param a first double
 * @param b second double
 * @return true if the first double is considered less than the second
 *              double
 */
public static boolean lessThan(double a, double b, double epsilon){
    return b - a > epsilon;
}
41
rich

Sie verwenden double NICHT, um Geld darzustellen. Niemals. Verwenden Java.math.BigDecimal stattdessen.

Dann können Sie festlegen, wie genau gerundet werden soll (was in Finanzanwendungen manchmal gesetzlich vorgeschrieben ist!) Und müssen keine dummen Hacks wie dieses Epsilon-Ding ausführen.

Im Ernst, die Verwendung von Gleitkommatypen zur Darstellung von Geld ist äußerst unprofessionell.

101

Ja. Java Doubles halten ihre Genauigkeit besser als Ihr angegebenes Epsilon von 0,00001.

Jeder Rundungsfehler, der aufgrund der Speicherung von Gleitkommawerten auftritt, tritt kleiner als 0,00001 auf. Ich benutze regelmäßig 1E-6 oder 0.000001 für ein doppeltes epsilon in Java ohne Probleme.

In einem ähnlichen Zusammenhang gefällt mir das Format von epsilon = 1E-5; weil ich der Meinung bin, dass es besser lesbar ist (1E-5 in Java = 1 x 10 ^ -5). 1E-6 ist beim Lesen von Code leicht von 1E-5 zu unterscheiden, wohingegen 0,00001 und 0,000001 sehen so ähnlich aus, wenn man sich den Code ansieht. Ich denke, sie sind der gleiche Wert.

11
Alex B

Whoa whoa whoa. Gibt es einen bestimmten Grund, warum Sie Gleitkommazahlen als Währung verwenden, oder wäre es besser, wenn Sie ein Zahlenformat mit willkürlicher Genauigkeit und Festkommazahlen verwenden? Ich habe keine Ahnung, was für ein spezifisches Problem Sie lösen wollen, aber Sie sollten sich überlegen, ob Sie wirklich mit einem halben Cent arbeiten möchten oder ob es sich nur um ein Artefakt handelt, wenn Sie ein ungenaues Zahlenformat verwenden.

8
Josh Lee

Wenn Sie BigDecimal verwenden können, verwenden Sie es, andernfalls:

/**
  *@param precision number of decimal digits
  */
public static boolean areEqualDouble(double a, double b, int precision) {
   return Math.abs(a - b) <= Math.pow(10, -precision);
}
6
carlosvin

Wenn Sie mit Geld zu tun haben, empfehle ich, das Money-Entwurfsmuster zu überprüfen (ursprünglich aus Martin Fowlers Buch über Unternehmensarchitektur ).

Ich schlage vor, diesen Link für die Motivation zu lesen: http://wiki.moredesignpatterns.com/space/Value+Object+Motivation+v2

5
Yuval Adam

Ich bin mit der Idee einverstanden, dass Double schlecht für Geld ist, aber die Idee, Double zu vergleichen, hat immer noch Interesse. Insbesondere ist die vorgeschlagene Verwendung von epsilon nur für Zahlen in einem bestimmten Bereich geeignet. Hier ist eine allgemeinere Verwendung eines Epsilons im Verhältnis zum Verhältnis der beiden Zahlen (Test für 0 entfällt):

boolean equal(double d1, double d2) {
  double d = d1 / d2;
  return (Math.abs(d - 1.0) < 0.001);
}
2
Bill

Gleitkommazahlen haben nur so viele signifikante Stellen, aber sie können viel höher sein. Wenn Ihre App jemals große Zahlen verarbeiten wird, werden Sie feststellen, dass der Epsilon-Wert unterschiedlich sein sollte.

0,001 + 0,001 = 0,002, ABER 12.345.678.900.000.000.000 + 1 = 12.345.678.900.000.000.000, wenn Sie Gleitkomma und Double verwenden. Es ist keine gute Darstellung von Geld, es sei denn, Sie sind sich verdammt sicher, dass Sie in diesem System niemals mehr als eine Million Dollar ausgeben werden.

1
Karl

Cent? Wenn Sie Geldwerte berechnen, sollten Sie keine Float-Werte verwenden. Geld ist eigentlich zählbare Werte. Die Cent oder Pennys usw. können als die zwei (oder was auch immer) am wenigsten signifikanten Ziffern einer ganzen Zahl betrachtet werden. Sie können Geldwerte als ganze Zahlen speichern und berechnen und durch 100 teilen (z. B. Punkt oder Komma zwei vor die beiden letzten Ziffern setzen). Die Verwendung von Floats kann zu seltsamen Rundungsfehlern führen ...

Wie auch immer, wenn Ihr epsilon die Genauigkeit definieren soll, sieht es ein bisschen zu klein (zu genau) aus ...

1

Wie andere Kommentatoren korrekt angegeben haben, sollten Sie niemals Gleitkomma-Arithmetik verwenden, wenn genaue Werte erforderlich sind, z. B. für Geldwerte. Der Hauptgrund ist in der Tat das Rundungsverhalten von Gleitkommawerten, aber vergessen wir nicht, dass der Umgang mit Gleitkommawerten auch den Umgang mit unendlichen und NaN-Werten erfordert.

Zur Veranschaulichung, dass Ihr Ansatz einfach nicht funktioniert, finden Sie hier einen einfachen Testcode. Ich füge einfach dein EPSILON zu 10.0 Hinzu und schaue, ob das Ergebnis gleich 10.0 Ist - was es nicht sein sollte, da der Unterschied eindeutig nicht ist weniger als EPSILON:

    double a = 10.0;
    double b = 10.0 + EPSILON;
    if (!equals(a, b)) {
        System.out.println("OK: " + a + " != " + b);
    } else {
        System.out.println("ERROR: " + a + " == " + b);
    }

Überraschung:

    ERROR: 10.0 == 10.00001

Der Fehler tritt aufgrund des Verlusts auf, wenn signifikante Bits bei der Subtraktion auftreten, wenn zwei Gleitkommawerte unterschiedliche Exponenten haben.

Wenn Sie darüber nachdenken, einen fortgeschritteneren "relativen Unterschied" -Ansatz anzuwenden, wie von anderen Kommentatoren vorgeschlagen, sollten Sie den ausgezeichneten Artikel von Bruce Dawson lesen Compareing Floating Point Numbers, Ausgabe 2012 , der zeigt, dass dieser Ansatz ähnliche Mängel aufweist und dass es tatsächlich keinen fehlersicheren ungefähren Gleitkomma-Vergleich gibt, der für alle Bereiche von Gleitkommazahlen funktioniert.

Um es kurz zu machen: Verzichten Sie auf doubles für Geldwerte und verwenden Sie genaue Zahlenangaben wie BigDecimal. Aus Gründen der Effizienz können Sie auch longs verwenden, das als "Millis" (Zehntel Cent) interpretiert wird, solange Sie Über- und Unterläufe zuverlässig verhindern. Dies ergibt einen maximal darstellbaren Wert von 9'223'372'036'854'775.807, Der für die meisten realen Anwendungen ausreichen sollte.

0
Franz D.