Frage von KingOff, 86

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?

Also genauer zu sagen :

ich bin dabei, Java zu erlernen und wollte da oben gennante Zeile ausprobieren, was aber "false" rausgibt.

Code lautet :

boolean gleich = (2.05-0.05) == 2.00; //habe auch mit 2 oder 2d probiert, geht nicht. System.out.print(gleich);

und das wars.

komischerweise gibt true, wenn ich schreb 2.1-0.1 == 2, was ist da der Fehler, verstehe nicht ?

MfG KingOff

Hilfreichste Antwort - ausgezeichnet vom Fragesteller
von Schachpapa, 47

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.

Kommentar von Suboptimierer ,

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?

Kommentar von Schachpapa ,

Danke ;-)

Operator overloading ist in Java bisher (bis Java 8) nicht vorgesehen. Das Festlegen einer bestimmten Toleranz als festes Epsilon würde auch nicht so einfach funktionieren, man müsste das Epsilon relativ zu den Operanden definieren, denn wenn ich 2.05e40 - 0.05e40 (also 5e42) rechne, wird die Differenz zu 2e40 sicher einigermaßen groß (aber im Verhältnis trotzdem klein sein). Dann lieber ohne Overloading bewusst die Unzulänglichkeiten von double einkalkulieren.

Kommentar von Schachpapa ,

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

Kommentar von PWolff ,

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

Kommentar von Unkreatiiiev ,

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

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

Kommentar von Schachpapa ,

Das ändert aber nichts am Problem, dass 0.05 keine "glatte" Zahl ist.

public class BD {
public static void main(String[] args) {
BigDecimal zwei = new BigDecimal("2.0");
BigDecimal zwei05 = new BigDecimal("2.05");
BigDecimal null05 = new BigDecimal("0.05");
BigDecimal erg = zwei05.subtract(null05);
System.out.println(erg);
System.out.println(erg == zwei);
System.out.println(erg.equals(zwei));
}
}
Ausgabe:
2.00
false
false
(übrigens diesmal auch bei 2.01)

Irgendwie hat mir bisher noch niemand schlüssig erklären können, warum man BigDecimal dem double vorziehen sollte, wenn 4 Nachkommastellen und 8 Vorkommastellen reichen. Es ist extrem umständlich, weil man kein Operator Overloading machen kann und deshalb alles als Methodenaufruf schreiben muss. Und es ist (ohne es jetzt getestet zu haben) durch den Overhead wahrscheinlich um Größenordnungen langsamer.

Kommentar von Unkreatiiiev ,

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.

Kommentar von Schachpapa ,

Ups, der klassische MNRE (manual not read error) :-)

Antwort
von ceevee, 34

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.

Kommentar von Schachpapa ,

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

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
von androhecker, 37

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
von LeCux, 15

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
von Gehilfling, 40

Sieh dir das mal an:

http://stackoverflow.com/questions/7408566/java-double-value-0-01-changes-to-0-0...

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

Kommentar von KingOff ,

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?

Kommentar von Gehilfling ,

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.

Keine passende Antwort gefunden?

Fragen Sie die Community