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

(1 votes)
Loading...

Similar Posts

Subscribe
Notify of
19 Answers
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Schachpapa
8 years ago

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.

Suboptimierer
8 years ago
Reply to  Schachpapa

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?

Schachpapa
8 years ago
Reply to  Suboptimierer

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.

Unkreatiiiev
8 years ago
Reply to  Schachpapa

Great. To eliminate the luck factor, there's the keyword "strictfp."

However, for maximum accuracy, it is better to use BigDecimal .

Schachpapa
8 years ago
Reply to  Unkreatiiiev

But that doesn't change the problem that 0.05 is not a "smooth" number.

 public class BD {
public static void main(String[] args) {
BigDecimal two = new BigDecimal("2.0");
BigDecimal two05 = new BigDecimal("2.05");
BigDecimal null05 = new BigDecimal("0.05");
BigDecimal erg = two05.subtract(null05);
System.out.println(erg);
System.out.println(erg == two);
System.out.println(erg.equals(two));
}
}
 Output:
2.00
false
false
(by the way, this time also at 2.01)

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.

Schachpapa
8 years ago

Ups, der klassische MNRE (manual not read error) 🙂

Unkreatiiiev
8 years ago

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.

PWolff
8 years ago

Man müsste natürlich ein epsilon in der Größenordnung der Genauigkeit der Zahlen nehmen.

Schachpapa
8 years ago

0.05e40 ist natürlich 5e38 nicht 5e42, sorry.

LeCux
8 years ago

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.

androhecker
8 years ago

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.

thomaszZz, Software Engineer bei gutefrage

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:

if (Math.abs((2.05 - 0.05) - 2.0) <= 0.000001)

ceevee
8 years ago

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.

Schachpapa
8 years ago
Reply to  ceevee

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

Aber unterm Strich bleibt auf jeden Fall
stehen, dass double und float nicht mathematisch
exakt sind.
Gehilfling
8 years ago

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”.

Gehilfling
8 years ago
Reply to  KingOff

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.

TheTeal
8 years ago

== 2 nicht 2.00 mal versucht?