Warum ergibt 5,0 minus 4,9 nicht 0,1?

SpaceCookie

Neues Mitglied
Moin moin,
ich hab ne Frage:
Warum ergibt das hier:
System.out.println(5.0 - 4.9);
das hier:
0.09999999999999964 ?
Ist ja ganz knapp dran, aber trotzdem falsch. Gibt es dafür ne Erklärung? Warum passiert das? Und: Kann ich das einfach durch Math.round() lösen?
Ich bin Anfänger btw.
Vielen Dank im Voraus!
 
Schauen wir uns nur einmal duale Zahlen an:

Vor dem Komma haben wir die Wertigkeiten 1, 2, 4, 8, 16., 32, ... (Halt 2 hoch 0, 1, 2, 3, ....)
Nach dem Komma hätten wir Wertigkeiten von 2 hoch -1, -2, -3, .... was dann den Werten:
0,5
0,25
0,125
0,0625
...
entspricht.

Da erkennst Du erst einmal schon, dass es keine wirklich einfache Darstellung von 0,1 (Dezimalsystem) gibt.

Da die Genauigkeit, mit der eine Zahl gespeichert wird, begrenzt ist, ist keine exakte Darstellung möglich und somit muss es zu diesen Ungenauigkeiten kommen.

Intern wird dann schon etwas gerundet. Der gespeicherte Wert bei 0.1 ist näher an 0,1 als an 0,10000000001 oder so (Die Anzahl der 0 ist jetzt nicht exakt ... ) aber nach der Rechnung hat man dies dann so nicht mehr und der Wert kommt nun näher an den Wert, den Du da gesehen hast.

Hier ist dann auch die Schlußfolgerung wichtig, denn diese Ungenauigkeiten können sich von Berechnung zu Berechnung immer weiter Vergrößern.
 
Schauen wir uns nur einmal duale Zahlen an:

Vor dem Komma haben wir die Wertigkeiten 1, 2, 4, 8, 16., 32, ... (Halt 2 hoch 0, 1, 2, 3, ....)
Nach dem Komma hätten wir Wertigkeiten von 2 hoch -1, -2, -3, .... was dann den Werten:
0,5
0,25
0,125
0,0625
...
entspricht.

Da erkennst Du erst einmal schon, dass es keine wirklich einfache Darstellung von 0,1 (Dezimalsystem) gibt.

Da die Genauigkeit, mit der eine Zahl gespeichert wird, begrenzt ist, ist keine exakte Darstellung möglich und somit muss es zu diesen Ungenauigkeiten kommen.

Intern wird dann schon etwas gerundet. Der gespeicherte Wert bei 0.1 ist näher an 0,1 als an 0,10000000001 oder so (Die Anzahl der 0 ist jetzt nicht exakt ... ) aber nach der Rechnung hat man dies dann so nicht mehr und der Wert kommt nun näher an den Wert, den Du da gesehen hast.

Hier ist dann auch die Schlußfolgerung wichtig, denn diese Ungenauigkeiten können sich von Berechnung zu Berechnung immer weiter Vergrößern.
Ok verstehe, vielen Dank. Das heißt aber, Runden (in meinem Fall auf eine Nachkommastelle) sollte das lösen, oder?
 
Ja, das würde das Problem lösen.

Solche Probleme hat man immer wenn man Fließkommazahlen rechnet. Es gibt da eigentlich hauptsächlich zwei Strategien:

- Runden auf die relevante Stellenanzahl. Je nach Rechnung, Anzahl Operationen und benötigter Genauigkeit kann es aber trotzdem sein, dass man am Ende ein "falsches" Ergebnis hat. (Wenn man z.B. bei einer Rechnung unbedingt 14 Nachkommastellen braucht und zig- Divsionen, Additionen etc. durchführt). Für die meisten praktischen Belange reicht es
- Einen Datentyp nehmen, der Operationen, die genau möglich sind, auch genau macht wie BigDecimal. Nachteil ist, dass man dann nicht einfach c = a + b schreiben kann sondern c=a.add(b) schreiben muss und insbesondere manche Rechenoperationen da teurer sind oder nicht so einfach möglich sind (Logarithmus und Co z.B.). BigDecimal nimmt man daher in der Regel wenn Abweichungen nicht tolerabel sind - also alles rund um Geldbeträge meistens.
 
- Einen Datentyp nehmen, der Operationen, die genau möglich sind, auch genau macht wie BigDecimal. Nachteil ist, dass man dann nicht einfach c = a + b schreiben kann sondern c=a.add(b) schreiben muss und insbesondere manche Rechenoperationen da teurer sind oder nicht so einfach möglich sind (Logarithmus und Co z.B.). BigDecimal nimmt man daher in der Regel wenn Abweichungen nicht tolerabel sind - also alles rund um Geldbeträge meistens.
Außerdem ist die Berechnung dabei ein Riesenaufriß.

Sowas wie c = a + b; rechnet die CPU in einem Takt oder sogar weniger durch. Ich habe mit BigDecimal noch nie gearbeitet, daher unausgegorenes Halbwissen, würde aber trotzdem mal vermuten daß da im Wesentlichen in Software "per Hand" gerechnet wird. Also anstatt einfach zwei Register in der CPU zu verknüpfen, z.B. über ein unvorhersehbar langes Array zu iterieren und in einfacher zu bewältigenden Häppchen wie Integer zu rechnen und mit dem Überlauf beim nächsten weiterzumachen.

Wenn du irgendwelche Matritzenoperationen z.B. in Simulationsprogrammen so durchführen willst, wo eine Matrix mehrere hundert oder gar tausend Zeilen/Spalten hat, kann das schnell richtig lange dauern.
 
Natuerlich braucht ein BigDecimal mehr Rechenleistung, aber in 99.9999% der Faelle ist der Unterschied in der Performance absolut unerheblich und zu ignorieren. Meiner Erfahrung nach schlieszen sich aber auch die Anforderungen "exaktes Ergebnis" und "schnelles Ergebnis" aus beziehungsweise treffen nie aufeinander. Also wenn du ein exaktes Ergebnis brauchst (BigDecimal), ist die Geschwindigkeit auf dieser Ebene nicht relevant, zum Beispiel in ERP Systemen. Wenn du wirklich Geschwindigkeit brauchst (float/double) ist dir egal ob das Ergebnis wirklich exakt ist, zum Beispiel in Grafikanzeige.

Aber wie gesagt, wir reden hier von Performance- und Speicher-Unterschieden auf schon fast esoterischer Ebene. Alternativ kann man immer einen int/long verwenden in der kleinsten Einheit, also zum Beispiel nicht "5,20 Euro" sondern "520 Eurocent".
 
Ich gebe dir zwar Recht daß Großgeschütze wie BigDecimal nur selten notwendig sind, aber:
Meiner Erfahrung nach schlieszen sich aber auch die Anforderungen "exaktes Ergebnis" und "schnelles Ergebnis" aus beziehungsweise treffen nie aufeinander. Also wenn du ein exaktes Ergebnis brauchst (BigDecimal), ist die Geschwindigkeit auf dieser Ebene nicht relevant, zum Beispiel in ERP Systemen. Wenn du wirklich Geschwindigkeit brauchst (float/double) ist dir egal ob das Ergebnis wirklich exakt ist, zum Beispiel in Grafikanzeige.
Ist das so? Deine Beispiele sagen mir jetzt wenig, aber wenn ich z.B. an Spice-Simulationen elektrischer Netzwerke denke, hast du von Natur aus oft schon sehr große Zahlenbereiche: Kapazitäten im Picofaradbereich, Nano- oder gar Picosekunden und darunter, bei Widerständen mit mehreren Mega- oder gern auch Gigaohm, spannt schonmal einen sehr weiten Zahlenbereich auf. 10^-15 bis 10^12 kann man da schonmal abdecken wollen. Bei zwei Operanden innerhalb derselben Operation.

Ähnlich dürfte es auch bei FEM-Simulationen aussehen, wird gerne für die Simulation von Kräften in einem Körper (z.B. Schwingung von Maschinengehäusen) oder Fluidsystemen oder bei der Simulation elektromagnetischer Felder.

Das wären Beispiele, wo ich an BigDecismal denken würde und bei all diesen Beispielen können schnell sehr lang andauernde, große Berechnungen entstehen. Ich habe heute öfter mal ein längeres Päuschen machen müssen wegen Wartezeit aus genau diesem Grund.
 
Hier im Projekt ist es umgekehrt - wer double (oder gar float) verwendet, bekommt was auf die Finger, hier wird nur BigDecimal (bzw. ein Wrapper drum rum für etwas mehr Convience) verwendet. Es kommt halt immer auf die Umgebung und Anforderungen an.
 
Du kannst es solange multiplizieren mit zehn dass es keine nachkommastellen mehr gibt und dann entsprechen die zahl genauso oft durch zehn teilen dadurch könnte man es umgehen zu runden
 
Du kannst es solange multiplizieren mit zehn dass es keine nachkommastellen mehr gibt und dann entsprechen die zahl genauso oft durch zehn teilen dadurch könnte man es umgehen zu runden
Nein, das ist keine Lösung sondern falsch. Das Problem besteht dann weiterhin.

Der Knackpunkt ist nicht Fragen Nachkommastellen, sondern die Verwendung von Fließkommazahlen. Das heißt, zum einen müsste man die Zahl dann auch in ein int/long umwandeln und zum anderen tritt das Problem auch beim Multiplizieren und dividieren auf.
 
Man könnte die Problematik mit der Ungenauigkeit beim Wandel in das duale System durch eine Darstellung mit 10er Exponent.
Da gibt es dann keine Normalisierung zu 1,xxxx aber eine exponentiell Darstellung ist denkbar:
49 E -1 = 4,9
5 E 0 = 5

Zur Subtraktion muss man den Exponent anpassen:
(5 × 10) E (0-1)
Wenn es bei der Mantisse oder dem Exponenten ein Überlauf gibt, dann muss man das behandeln.

Aber so hat man 50E-1 - 49E-1 = 1E-1

Theoretisch kein Thema ...
 
Ist ja ganz knapp dran, aber trotzdem falsch. Gibt es dafür ne Erklärung? Warum passiert das? Und: Kann ich das einfach durch Math.round() lösen?
1/10 ist im Dualsystem genau so wenig exakt darstellbar wie 1/3 im Dezimalsystem. Die Brüche lassen sich mit einer begrenzten Zahl an Nachkommastellen im jeweiligen Zahlensystem nicht genau darstellen. Der Umgang mit solchen Problemen unterscheidet sich vom Dezimalsystem nicht.
 

Zurück
Oben