Java, boolean equals = 2.05-0.05 == 2.00; actually this should return TRUE, but returns false, with boolean equals = 2.1-0.1 = 2.00; it returns True, why?
To be more precise:
I'm learning Java and wanted to try the line mentioned above, but it returns "false".
Code is:
boolean equals = (2.05-0.05) == 2.00; //I also tried with 2 or 2d, doesn't work. System.out.print(equals);
and that was it.
Strangely, it returns true if I write 2.1-0.1 == 2. What's the error? I don't understand.
Kind regards, KingOff
Der Java double Typ ist intern keine Dezimalzahl sondern ein Binärbruch. Abbrechende Binärbrüche sind nur die, die im Nenner eine Zweierpotenz haben, also halbe, Viertel, Achtel usw. Alles andere sind nicht abbrechende Binärbrüche (ähnlich wie 1/3 = 0.3333… oder 1/7 = 0.142857142…)
float und double stellen eine bestimmte begrenzte Anzahl von Binärstellen zur Verfügung, am Ende wird gerundet oder abgeschnitten. Mit Glück (2.01-0.01) geht es dann glatt auf, mit Pech (2.05-0.05) bleibt ein minimaler Fehler (die ersten 10 Stellen bei 2.0000000001 sind richtig, der Unterschied liegt in der Größenordnung von Millionstel Prozent.
Jeder weiß dass 8/3 – 2/3 = 2 ist, aber wenn du es als Dezimalbruch schreibst und nur 5 Stellen zur Verfügung hast, musst du 2.6667 – .66667 rechnen (die führende Null zählt nicht) und dann kommt 2.0001 heraus.
Deshalb soll man Ergebnisse von Fließkommaberechnungen nie mit == vergleichen. Stattdessen z.B. Math.abs(x – y) < 0.000001
Wenn möglich sind Ganzzahberechnungen vorzuziehen.
Sehr gute und einleuchtende Begründung!
Kann man in Java eigentlich Operatoren überladen, z. B. so, dass automatisch mit einer Toleranz von einem festen Epsilon verglichen wird?
Thanks 😉
Operator overloading is not currently supported in Java (until Java 8). Specifying a specific tolerance as a fixed epsilon wouldn't work that easily either; you would have to define the epsilon relative to the operands. If I calculate 2.05e40 – 0.05e40 (i.e., 5e42), the difference to 2e40 will certainly be quite large (but still relatively small). It's better to consciously factor in the shortcomings of double without overloading.
Great. To eliminate the luck factor, there's the keyword "strictfp."
However, for maximum accuracy, it is better to use BigDecimal .
But that doesn't change the problem that 0.05 is not a "smooth" number.
Somehow, no one has yet been able to explain to me convincingly why BigDecimal should be preferred to double when 4 decimal places and 8 pre-decimal places are sufficient. It's extremely cumbersome because you can't do operator overloading, so you have to write everything as a method call. And (without having tested it yet), the overhead probably makes it orders of magnitude slower.
Ups, der klassische MNRE (manual not read error) 🙂
Hab’ ich auch nicht gesagt. BigDecimal bietet einfach nur mehr Genauigkeit.
Bei deinem Beispiel ist das Problem, dass “2.0” nicht gleich “2.00” ist. Benutz’ compareTo();
Steht auch alles in den Docs.
Man müsste natürlich ein epsilon in der Größenordnung der Genauigkeit der Zahlen nehmen.
0.05e40 ist natürlich 5e38 nicht 5e42, sorry.
Gleitkommazahlen auf Gleichheit zu vergleichen ist IMMER falsch. Immer. Also wirklich immer.
Das hat mit der internen Darstellung der Zahlen nach IEEE zu tun – sobald man rechnet und sich der Gleitkomma-Teil in einer Potenz unterscheidet sind die Zahlen fast immer nicht mehr gleich, auch wenn sie das in unserem Verständnis sein sollten.
Fließkommazahlen wie double und float sind in dem Punkt ziemlich nervig.
Oftmals kommt bei einer Addition/Subtraktion dann zum Beispiel statt 2 1,9999999 raus (vereinfacht). Lösungen gibt es viele, die Klasse BigDecimal verwendet eine andere Methode und man kann die Zahlen vor der Verrechnung auch multiplizieren.
Grundsätzlich gilt: Gleitkommazahlen (Float, Double) niemals mit == vergleichen. Das Problem liegt darin, dass diese intern nicht als Zahlenfolge, sondern als Exponent und Mantisse gespeichert werden, was dazu führt, dass sich vermeintlich simple Werte nicht korrekt darstellen lassen.
Daher vergleicht man diese i.d.R. mit einem Delta:
Floating point numbers (double / float) are stored in Java in the format
Sign * 1, mantissa * 2 ^ exponent. This is defined by the standard.
https://de.wikipedia.org/wiki/IEEE\_754#General.
If we assume in your example that we do not care about the sign and the mantissa is 0, then the formulas for your variables are
2.05 = 2 ^ E1
0.5 = 2 ^ E2
This is the format your computer stores numbers in. You can calculate E1 and E2: E1 is log_2 (2.05) = 1.036, E2 is -4.322. I used three decimal places because I don't have infinite storage space for the exponent.
However, if you now perform the opposite test, i.e. (2^1.036) – (2^-4.322), you would expect the result to be 2. The result, however, is 2.00053696776. Close to 2, but not exactly 2. In your other example (2.1 – 0.1), you're probably just lucky that it's accurate.
If you want to compare floating point numbers for equality, you should always use a comparison with tolerance. For more information, see ThomaszZz's answer.
Sorry, aber das stimmt nicht ganz (siehe jedoch unten letzter Satz)
Exponent und Mantisse sind ganzzahlig.
2 = binär 10 = 1.0 * 2^1 daher Mantisse (ohne führende 1) 0 und Exp auch 1
2.5 = 10.1 = 1.01 * 2^1 M = 01000… E = 1
2.05 = 10.0000110011001100110011001100110011…
M = 00000110011… E = 1
0.05 = 0.0000110011001100110011001100110011…
M = 1001100110… E = -5
Bei der Addition bzw. Substraktion werden zunächst die Exponenten gleichnamig gemacht (dabei gehen bei 0.05 hinten Stellen verloren) und dann addiert.
Vgl.: https://de.wikipedia.org/wiki/IEEE_754#Allgemeines und
http://www.arndt-bruenner.de/mathe/scripts/Zahlensysteme.htm
Sieh dir das mal an:
http://stackoverflow.com/questions/7408566/java-double-value-0-01-changes-to-0-009999999999999787
Double 0.01 != 0.01, das wird dann “ungefähr” 0.01, aber eben nicht genau. Demnach wird deine Gleichung auch nicht genau 0, sondern nur “ungefähr”.
If you subtract 0.05 from 2.05, you get exactly 2 ruas? Mathematically, I'm right. You're right, that gives 1.999999999999998, but why?
Klar stimmt das mathematisch. Aber der PC kann eben nicht alles unendlich genau darstellen. Drum sind kleine Kommazahlen auch nicht exakt der Wert, den du möchtest, sondern werden nur angenähert.
Wenn du auf dem Papier 2.0005 – 0.0005 rechnest, kommt 2 raus. Der PC macht das aber nicht so und daher kommt ein krummer Wert raus, der eben nicht genau 2 entspricht.
== 2 nicht 2.00 mal versucht?
I tried 2.00 — 2 — and 2d (double), but it always results in false, with 2.1 – 0.1 == 2 the correct result comes out