Layer 8Ehrlicher Tech-Support

0.1 + 0.2 ergibt nicht 0.3 – Fließkomma und Centbeträge

Der Rechner liefert 0.30000000000000004 und die Summe stimmt um einen Cent nicht. Kein Fehler: Binär lässt sich 0,1 nicht exakt darstellen.

Aufwand:
ca. 15 Min.
Voraussetzung:
Python 3, Ein Taschenrechner, dem du kurz misstraust
Gilt für:
Python 3, Jede Sprache mit IEEE-754-Fließkomma (C, Java, JavaScript, C#)

Das Symptom

Der Taschenrechner im Kopf sagt 0,3. Das Programm sagt:

>>> 0.1 + 0.2
0.30000000000000004
>>> 0.1 + 0.2 == 0.3
False

In der Tabelle oder im Warenkorb sieht es harmloser und ärgerlicher aus: Drei Posten, die zusammen 39,99 Euro ergeben müssten, stehen intern als 39.989999999999995 – und wer sie abschneidet statt zu runden, bucht 39,98. Die Umsatzliste weicht dann um einen Cent vom Kontoauszug ab, und niemand findet den fehlenden Betrag, weil er nirgends liegt.

Die „Layer 8“-Wahrheit

Der Computer hat recht, die Erwartung war falsch. Fließkommazahlen speichern Brüche im Zweiersystem, und im Zweiersystem ist ein Zehntel genauso unendlich periodisch wie ein Drittel im Zehnersystem: 0,0001100110011… ohne Ende. Nach 53 Bit Präzision – der doppelten Genauigkeit nach IEEE 754, die praktisch jeder Prozessor seit den 2000er-Jahren ausführt – bricht die Darstellung ab. Was gespeichert wird, ist eine sehr gute Näherung an 0,1. Nur eben keine 0,1. Zwei Näherungen addiert ergeben eine dritte Näherung, und die trifft 0,3 nicht auf den letzten Bit genau. Das ist kein Bug in Python, nicht in Excel und nicht im Prozessor: Es ist dieselbe Norm, die auch C, Java und JavaScript benutzen, und sie tut exakt das, was in ihr steht.

Der eigentliche Layer-8-Anteil ist der Reflex, das Problem mit round() zuzukleistern. Runden macht die Anzeige hübsch und den Fehler unsichtbar, aber nicht kleiner – und round() selbst hat zwei dokumentierte Eigenheiten, die nachts noch für Diskussionen sorgen: Bei genau gleichem Abstand rundet Python zur geraden Zahl, und round(2.675, 2) liefert 2.67, weil 2,675 nie im Speicher stand. Wer mit Geld rechnet, hört deshalb auf, Geld als Kommazahl zu behandeln: intern ganze Cent, oder ein Dezimaltyp wie decimal, der ausdrücklich für Buchhaltung gedacht ist. Gerundet wird einmal, ganz am Schluss, für die Augen des Lesers. Alles andere ist der Versuch, mit einer Näherung eine Bilanz zu führen.

Die echte, verifizierte Lösung

1000 % faktenbasiert. Jeder Befehl ist real, jeder Pfad existiert. Schritt für Schritt:

  1. 1.

    Erst nachrechnen, dann fluchen

    Bevor du die Bibliothek verdächtigst, lass dir das Ergebnis roh ausgeben. Die zusätzlichen Stellen sind kein Anzeigefehler, sie sind der tatsächlich gespeicherte Wert. Der Vergleich auf Gleichheit scheitert deshalb völlig zu Recht.

    python3 -c "print(0.1 + 0.2); print(0.1 + 0.2 == 0.3)"
    # 0.30000000000000004
    # False
    
  2. 2.

    Den Beleg im Alltag suchen: drei Positionen, ein Cent daneben

    Das Phänomen bleibt kein Laborbeispiel. Sobald mehrere Beträge zusammenkommen, wandert die Abweichung in die zweite Nachkommastelle – genau dorthin, wo bei Geld die Wahrheit wohnt.

    python3 -c "print(19.99 + 0.01 + 19.99); print(sum([19.99, 5.01, 3.30]))"
    # 39.989999999999995
    # 28.299999999999997
    
  3. 3.

    Geld in ganzen Cent rechnen

    Die robusteste Lösung ist die langweiligste: intern nur Ganzzahlen speichern, nämlich Cent statt Euro. Ganzzahlen kennen keine Rundungsfehler, und die Kommastelle entsteht erst bei der Ausgabe. Datenbanken und Kassensysteme machen es seit Jahrzehnten so.

    python3 -c "s = 1999 + 1 + 1999; print(s, f'{s // 100},{s % 100:02d} Euro')"
    # 3999 39,99 Euro
    
  4. 4.

    Wo es dezimal bleiben muss: das Modul decimal

    Wenn Prozentsätze, Steuersätze oder Wechselkurse ins Spiel kommen, wird die Cent-Ganzzahl unhandlich. Dann nimmt man einen Dezimaltyp. Die Python-Dokumentation empfiehlt genau dafür das Modul `decimal`, das eine für Buchhaltung geeignete Dezimalarithmetik umsetzt. Wichtig: die Werte als Zeichenkette übergeben, sonst füttert man den Dezimaltyp mit einer bereits verfälschten Fließkommazahl.

    python3 -c "from decimal import Decimal; print(Decimal('0.1') + Decimal('0.2') == Decimal('0.3'))"
    # True
    
  5. 5.

    Fließkommazahlen nie auf Gleichheit prüfen

    Wo Fließkomma bleiben darf – Messwerte, Geometrie, Physik –, ersetzt man den Gleichheitstest durch einen Nähetest. Die Standardbibliothek bringt ihn mit, man muss keine eigene Epsilon-Konstante erfinden.

    python3 -c "import math; print(math.isclose(0.1 + 0.2, 0.3))"
    # True
    
  6. 6.

    Erst bei der Ausgabe runden – und wissen, was round() tut

    Gerundet wird zum Schluss, nie zwischendurch. Und man sollte die zwei dokumentierten Eigenheiten kennen: Bei gleichem Abstand rundet Python zur geraden Zahl, und `round(2.675, 2)` ergibt 2.67 statt 2.68, weil 2,675 gar nicht exakt im Speicher steht.

    python3 -c "print(round(0.5), round(1.5), round(2.5)); print(round(2.675, 2))"
    # 0 2 2
    # 2.67
    

Häufige Fragen (FAQ)

?Ist das ein Fehler in Python, in Excel oder in meinem Prozessor?

In keinem davon. Die Python-Dokumentation ist da ungewöhnlich deutlich: Seit mindestens 2000 benutzen fast alle Maschinen IEEE-754-Fließkomma-Arithmetik, und fast alle Plattformen bilden Python-Fließkommazahlen auf IEEE 754 binary64 ab, also auf die sogenannte doppelte Genauigkeit mit 53 Bit Präzision. Die FAQ führt als Beispiel `1.2 - 1.0` an, was 0.19999999999999996 ergibt: Der für 1,2 gespeicherte Wert ist eine sehr gute Näherung – aber eben nicht exakt 1,2. Zur Frage, ob das ein Bug sei, schreibt die Dokumentation trocken: `It's not.` Dasselbe Verhalten siehst du in C, Java und JavaScript, weil dort dieselbe Hardware dieselbe Norm ausführt.

?Warum fällt das ausgerechnet bei Geldbeträgen auf?

Weil Geld dezimal gedacht ist und der Rechner binär rechnet. Ein Zehntel ist im Zweiersystem ein unendlich periodischer Bruch – die Dokumentation schreibt ihn als 0.0001100110011001100110011001100110011001100110011… aus. Nach 53 Bit ist Schluss, der Rest wird abgeschnitten. Bei einem Messwert interessiert dieser Fehler niemanden, bei einer Rechnung mit 3000 Positionen summiert er sich zu einem sichtbaren Cent – und dann diskutieren zwei Menschen eine Stunde lang über eine Zahl, die beide falsch verstanden haben.

?Reicht es nicht, einfach am Ende auf zwei Stellen zu runden?

Für die Anzeige ja, für die Buchhaltung nein. Runden versteckt den Fehler, es entfernt ihn nicht – und `round()` hat selbst zwei Eigenheiten, die überraschen. Erstens rundet Python bei genau gleichem Abstand zur geraden Möglichkeit hin, `round(0.5)` ist also 0 und `round(1.5)` ist 2. Zweitens gibt laut Dokumentation `round(2.675, 2)` das Ergebnis 2.67 statt der erwarteten 2.68, weil sich die meisten Dezimalbrüche nicht exakt als Fließkommazahl darstellen lassen. Wer Beträge addiert, rechnet besser in ganzen Cent oder mit `decimal` und rundet erst bei der Ausgabe.

Belege

Diese Seite ist gegen die folgenden Primärquellen geprüft – Hersteller, Standard oder Gesetzestext. Wer es nicht glaubt, muss es nicht: Da steht, wo es nachzulesen ist.

Zuletzt geprüft am . Dokumentation altert – ein Datum daneben ist der einzige ehrliche Umgang damit.

Auch zu finden unter

Verwandte Fehler

SWAnfängerfehlerexternally-managed-environment

pip: error: externally-managed-environment

pip install bricht mit 'externally-managed-environment' ab? So installierst du Python-Pakete korrekt per venv oder pipx statt am System.

SWAnfängerfehlerModuleNotFoundError

Python: ModuleNotFoundError: No module named X

Python meldet ModuleNotFoundError, obwohl das Paket installiert ist? Meist stimmt die Umgebung, der Import-Name oder der Dateiname nicht. So löst du es.

SWAnfängerfehlerTabError: inconsistent use of tabs and spaces in indentation

TabError: inconsistent use of tabs and spaces

Python bricht ab, weil Tabs und Leerzeichen gemischt wurden. Der Code sieht korrekt aus – die Einrückung ist es nicht.

SWAnfängerfehlerRuntimeError: Expected all tensors to be on the same device

PyTorch: Expected all tensors to be on the same device

Modell auf der GPU, Daten auf der CPU: PyTorch verschiebt nichts von allein. Warum das so ist und wie beide Seiten auf dasselbe Gerät kommen.

SWLästigCERTIFICATE_VERIFY_FAILED

pip: SSL: CERTIFICATE_VERIFY_FAILED

pip lädt keine Pakete wegen SSL-Zertifikatsfehler? Meist schneidet ein Firmen-Proxy mit oder die Zertifikate sind veraltet — beides ist lösbar.