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

(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

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.

Unkreatiiiev
8 years ago
Reply to  Schachpapa

Top. Um den Glücksfaktor herauszunehmen, gibt’s das Schlüsselwort “strictfp”.

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

Schachpapa
8 years ago
Reply to  Unkreatiiiev

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.

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

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.

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?