Java , boolean gleich = 2.05-0.05 == 2.00; eigentlich sollte das TRUE ergeben, ergibt aber false, bei boolean gleich = 2.1-0.1 = 2.00; kommt True raus, warum?

...komplette Frage anzeigen

6 Antworten

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.

Antwort bewerten Vielen Dank für Deine Bewertung
Kommentar von Suboptimierer
05.08.2016, 12:44

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?

0
Kommentar von Unkreatiiiev
05.08.2016, 14:31

Top. Um den Glücksfaktor herauszunehmen, gibt's das Schlüsselwort "strictfp".

Trotzdem sollte man für maximale Genauigkeit lieber BigDecimal verwenden.

1

Fließkommazahlen (double / float) werden in Java in dem Format

Vorzeichen * 1,Mantisse * 2 ^ Exponent gespeichert. Das ist so im Standard festgelegt.

https://de.wikipedia.org/wiki/IEEE\_754#Allgemeines.

Wenn wir in deinem Beispiel mal davon ausgehen, dass uns das Vorzeichen egal ist und die Mantisse 0 ist, dann ergeben sich für deine Variablen die Formeln

2.05 = 2 ^ E1

0.5 = 2 ^ E2

In diesem Format speichert dein Computer die Zahlen ab. E1 und E2 kannst du ausrechnen, E1 ist log_2 (2.05) = 1.036, E2 ist -4.322. Ich hab 3 Nachkommastellen genommen, denn ich hab nicht unendlich viel Speicherplatz für den Exponenten. 

Wenn du jetzt allerdings die Gegenprobe ausführst, also (2^1.036) - (2^-4.322), dann würdest du 2 als Ergebnis erwarten. Das Ergebnis ist allerdings 2.00053696776. Dicht an der 2 dran, aber nicht genau 2. Bei deinem anderen Beispiel (2.1 - 0,1) hast du mit der Genauigkeit wahrscheinlich einfach nur Glück, dass es da passt.

Wenn man Fließkommazahlen auf Gleichheit vergleichen will, dann sollte man immer einen Vergleich mit Toleranz nutzen, dazu schaust du mal in die Antwort von ThomaszZz.

Antwort bewerten Vielen Dank für Deine Bewertung
Kommentar von Schachpapa
05.08.2016, 13:23

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

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.

Antwort bewerten Vielen Dank für Deine Bewertung

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.

Antwort bewerten Vielen Dank für Deine Bewertung

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

Antwort bewerten Vielen Dank für Deine Bewertung
Kommentar von KingOff
05.08.2016, 12:11

Wenn man von 2.05   0.05 abzieht kommt genau 2 ruas? Mathematisch gesehen habe ich recht. Du hast recht, daraus kommt 1.99999999999998 aber wieso?

0

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)

Antwort bewerten Vielen Dank für Deine Bewertung

Was möchtest Du wissen?