Niemals mit den Datentypen Float oder Double Geldbeträge berechnen!
Das jedenfalls zeigt eine Rechnung die Michael Haupt erhalten und unter dem Titel “Ich bin reich!” veröffentlicht hat:
Auf der Rechnung ist schön zu erkennen, dass 38,29 EUR – 38,29 EUR nicht immer 0 ergeben. Jedenfalls dann nicht, wenn man mit Fließkommazahlen rechnet. Und da der ausgewiesene Restbetrag negativ ist steht jemand gewaltig bei Michael Haupt in den Miesen. Dieser hat diese Tatsache wiederum dem Rechnungsschreiber auch umgehend in einem Brief mitgeteilt:
Der offene Restbetrag ist mit EUR -7,11*10^15 ausgewiesen. Da es sich hierbei um einen negativen Betrag handelt, stehen Sie bei mir in der Schuld. Ich hege Zweifel daran, dass Sie 7,11 Billiarden Euro auf einen Schlag aufbringen können; wir können uns gern auf eine Ratenzahlung einigen.
Generell sollte man sich aus diesem Beispiel mitnehmen, dass bei der Berechnung von Fließkommazahlen Rundungsfehler entstehen können. Das kann man sogar relativ einfach in einem kleinen Beispiel nachstellen:
System.out.println("1.0 - 1.0 = " + new Double(1.0 - 1.0)); System.out.println("1.0 - 0.9 = " + new Double(1.0 - 0.9)); System.out.println("1.0 - 0.8 = " + new Double(1.0 - 0.8)); System.out.println("1.0 - 0.7 = " + new Double(1.0 - 0.7)); System.out.println("1.0 - 0.6 = " + new Double(1.0 - 0.6)); System.out.println("1.0 - 0.5 = " + new Double(1.0 - 0.5)); // Ausgabe: // 1.0 - 1.0 = 0.0 // 1.0 - 0.9 = 0.09999999999999998 // 1.0 - 0.8 = 0.19999999999999996 // 1.0 - 0.7 = 0.30000000000000004 // 1.0 - 0.6 = 0.4 // 1.0 - 0.5 = 0.5
In dem Artikel von Michael Haupt wird auch noch auf die Regel 48 aus dem Buch “Effective Java” hingewiesen. Diese lautet “Item 48: Avoid float and double if exact answers are required” (siehe auch im Inhaltsverzeichnis).
Aufgrund dieses Blog Artikels und anderen zuvor zufällig gefundenen positiven Hinweisen auf das Buch “Effective Java” habe ich mir dieses zugelegt um die verschiedenen Empfehlungen mal durchzublättern. In dem besagten Item 48 wird ausführlich auf das Rundungsproblem bei Floats und Doubles eingegangen und unter anderem das folgende sehr schöne Java Schnipsel gezeigt:
double guthaben = 1.00; int gekaufteTeile = 0; for (double preis = .10; guthaben >= preis; preis += .10) { guthaben -= preis; gekaufteTeile++; System.out.println("Preis: " + preis); System.out.println("Guthaben: " + guthaben); } System.out.println("Gekaufte Teile: " + gekaufteTeile); System.out.println("Restgeld: " + guthaben); // Ausgaben: // Preis: 0.1 // Guthaben: 0.9 // Preis: 0.2 // Guthaben: 0.7 // Preis: 0.30000000000000004 // Guthaben: 0.3999999999999999 // Gekaufte Teile: 3 // Restgeld: 0.3999999999999999
In diesem Code Beispiel wird ein Betrag von einem Guthaben abgezogen und mit dem Restbetrag weitergerechnet wobei sich der Preis für ein Teil erhöht. Dabei müsste man mit einem Guthaben von 1.0 und dem Preis und mit dem steigendem Preis von 0.10 exakt 4 Teile kaufen können. Bei der ersten Iteration kostet das Teil 0.1, bei der zweiten 0.2, bei der dritten 0.3 und bei der vierten 0.4. Durch den besagten Rundungsfehler, welcher in dieser Schleife bei der dritten Iteration auftritt, kommt es nicht mehr zum Kauf des vierten Teils. Schlecht für den Verkäufer denn er hat ein Teil weniger verkauft und schlecht für den Käufer denn dieser hat jetzt einen kleineren Betrag als 4.0 am Ende raus (auch wenn die Differenz gegen 0 geht).
Somit gilt ganz allgemein der Hinweis: Entwickelt man eine Finanzsoftware sollte man im eigenen Interesse immer mit BigDecimal, Long oder Integer arbeiten!
Am Ende noch kurz der Hinweis darauf, dass es sich durchaus lohnt das Buch Effective Java einmal durchzublättern bzw. zu kaufen und ordentlich durchzulesen. Man kann sich dadurch einige Probleme ersparen und zudem noch die Qualität des eigenen Codes verbessern.
(via denkspuren)
Wenn du Fragen oder Anregungen zum Post hast, dann hinterlasse doch einen Kommentar oder wenn du weiterhin Artikel von Javathreads lesen möchtest, dann abonniere den RSS Feed und sehe direkt in deinem Feed Reader die nächsten Artikel.
Ähnliche Artikel, die dich interessieren könnten:
Kommentare
Jopp – immer BigDecimal verwenden. Wahrscheinlich nur rechtlichen Gründen gibt im JDK keine Money-Klasse. Das mit den floats / doubles bei Geldbeträgen sollte echten Informatikern nicht passieren…. oder doch?
Ich habs bei der Erläuterung oben zu dem zweiten Java Schnipsel total verrafft. Der Preis wird natürlich nach oben gezählt und somit würde man insgesamt 4 Teile kaufen können. Gibt man den Preis in jeder Iteration aus erhält man folgende Ausgabe:
Preis: 0.1 Preis: 0.2 Preis: 0.30000000000000004
Durch den Rundungsfehler ist es nicht mehr möglich das letzte Teil für 0.4 zu kaufen. Obwohl er rein rechnerisch ingesamt exakt 4 Teile kaufen können müsste. (Ich habe jetzt oben im zweiten Beispiel den Java Schnipsel und Erläuterungstext angepasst – danke für den Hinweis)
Zu dem ersten Beispiel: es kann schon sein, dass dort nicht nur ein Rundungsfehler die Ursache ist. Aber es ist dennoch ein schönes Beispiel warum man eher BigDecimal oder Long anstatt Float oder Double für exakte Berechnungen nehmen sollte.
BigDecimal, Long oder Integer kann man für exakte Berechnungen natürlich verwenden. Das Ergebnis wird richtig sein. Aber warum fehlen long und int in der Aufzählung der Möglichkeiten? Die sind in jedem Fall performanter!
Kleine Anmerkung zu deinem ersten Beispiel
System.out.println("1.0 - 1.0 = " + new Double(1.0 - 1.0));
System.out.println("1.0 - 0.9 = " + new Double(1.0 - 0.9));
System.out.println("1.0 - 0.8 = " + new Double(1.0 - 0.8));
System.out.println("1.0 - 0.7 = " + new Double(1.0 - 0.7));
System.out.println("1.0 - 0.6 = " + new Double(1.0 - 0.6));
System.out.println("1.0 - 0.5 = " + new Double(1.0 - 0.5));
Das muss nicht auf allen Rechnern zu Fehlern führen. Es kommt immer darauf an, wie die Fließkommazahlen berechnet werden. Auf manchen Rechnern führt bspw. 1.0 – 0.9 zu Fehlern, auf Anderen 1.0 – 0.8 und auf wieder Anderen weder das Eine, noch das Andere.
Grüße
Stefan
@Rainer: natürlich kann man auch die primitiven Datentypen verwenden – hab’s einfach vergessen zu erwähnen ;)
@Stefan: hast Recht – das Beispiel sollte auch nur vor Augen führen wie der mögliche Rundungsfehler aussehen kann. Ich glaube man sollte einfach nur wissen, dass es zu einem Rundungsfehler kommen könnte.
es gibt auch 2 artikel bei sun zu dem thema:
http://java.sun.com/mailers/techtips/corejava/2007/tt0707.html#2
http://java.sun.com/developer/JDCTechTips/2001/tt0807.html#tip1
da bin ich mal drüber gestolpert, als ich nämlich genau so problem hatte ;)















Ich möchte ja nichts sagen, aber der Code im letzten Listing passt nicht zur Anforderung. In deinem Fall erhöht sich der Preis pro Kauf, dann ist es klar, dass er nur 6 Stück kaufen kann.
Denn auch wenn Rundungsfehler auftreten: Sie sind im einzelnen so klein, dass man es kaum merkt und aufgereiht ist der Unterschied erst bei sehr hohen Stückzahlen relevant (6 Stück sind eher wenige).
Wenn die Differenz von erwartetem Ergebnis und eingetretenem Ergebnis > 1 (Anzahl, Stück, ..) sollte man misstrauisch werden. Dann handelt es sich eher um einen Programmfehler, als um einen Rundungsfehler.
Noch eine Anmerkung zum obersten Beispiel. Da scheint die Formatierung des Ergebnisses nicht zu stimmen. Es ist viel Wahrscheinlicher, das da -7,11*10^-15 stehen müsste. Denn der Rundungsfehler zwischen zwei auf zwei Nachkommastellen gerundeten Zahlen kann nicht größer als 0.00999.. sein.