Sie prüfen die Belege einzeln, und jeder Betrag stimmt… und doch weicht die Monatssumme der Verkäufe um einen Cent vom Bankeingang ab. Oder Sie haben Preis × Steuersatz in einer FLOAT-Spalte gespeichert, und die SUM will partout nicht aufgehen. Die meisten dieser Rätsel vom Typ „jede Zeile stimmt, nur die Summe nicht“ sind keine Fehler in Ihrem Code. Sie entstehen, weil Gleitkommazahlen (FLOAT) die meisten Werte nur näherungsweise speichern können.
In diesem Artikel schauen wir uns an, warum die Summen davonlaufen – mit Formeln und interaktiven Grafiken zum Selbst-Ausprobieren.
Die Abbildungen 1, 2 und 3 sind lebendige Grafiken, direkt auf dieser Seite bedienbar. Sie dürfen gern zuerst mit den Abbildungen spielen und danach zum Text zurückkehren.
Wenn Sie eigentlich eine Entscheidungshilfe für Spaltentypen suchen, finden Sie die in SQL-Datentypen richtig wählen (der Klassiker „Geld in FLOAT“ samt Entscheidungsleitfaden FLOAT vs. DECIMAL).
Was dieser Artikel beantwortet
| Frage | Wo |
|---|---|
| Warum macht ein Alltagsdezimal wie 0.1 überhaupt Ärger? | §1 / Abb. 1 |
| Warum taucht ein in einer Zeile unsichtbarer Fehler in der Summe auf? | Abb. 2 |
| Laufen FLOAT und DECIMAL bei denselben Belegen wirklich auseinander? | Abb. 3 / Tabelle 1 |
| Was hat das berühmte 0.1 + 0.2 ≠ 0.3 damit zu tun? | §5 |
| Was heißt das für die Praxis? | §6 / FAQ |
Um mit einer festen Zahl von Bytes einen riesigen Zahlenbereich abzudecken, speichern FLOAT / DOUBLE die meisten Dezimalbrüche als auf den nächstgelegenen darstellbaren Wert gerundete Näherung. Dieser Fehler ist kein Defekt, sondern ein bewusster Tausch: Geschwindigkeit und Wertebereich gegen Exaktheit.
Genau deshalb passen diese Typen schlecht zu Zahlen, die auf den letzten Cent stimmen müssen: Preise, Steuersätze, Rechnungssummen. Dort ist DECIMAL (NUMERIC), das Dezimalstellen garantiert, die richtige Antwort. Bei Größen, die ohnehin Messrauschen tragen – Sensordaten, Statistiken –, spielt die winzige Rundung von FLOAT in der Praxis meist keine Rolle.
Ein hilfreiches Bild: ein Lineal, dem Teilstriche fehlen. Das denkbar gewöhnlichste Dezimal 0.1 trifft keinen einzigen Teilstrich des binären Lineals; es wird zum nächstgelegenen Strich gezogen, und wiederholtes Addieren stapelt diese kleinen Verschiebungen. Das ist die Anatomie des einen Cents, um den die Monatssumme daneben liegt.
1. Warum das dezimale 0.1 dem Binärsystem Mühe macht
Auf dem Papier ist 0.1 ein sauberes, endliches Dezimal. Im Rechner stellt FLOAT Zahlen jedoch binär dar (Nullen und Einsen), nach dem Standard IEEE 754. Wandelt man 0.1 ins Binäre um, wiederholt es sich unendlich.
\[ 0.1_{10} = 0.0001100110011\ldots_{2} \]
Mit endlich vielen Bits muss der Wert abgeschnitten und auf den nächsten Teilstrich gerundet werden. Schreibt man diese Operation als \(\mathrm{fl}(x)\), wird tatsächlich nicht \(x\) gespeichert, sondern \(\mathrm{fl}(x)\).
\[ \mathrm{gespeichert}(x) = \mathrm{fl}(x) \neq x \quad (\text{für viele Dezimalbrüche}) \]
Ein einzelner Rundungsschritt ist meist winzig. Ärger gibt es, wenn man die Näherung wieder und wieder addiert.
2. Die Teilstriche, die FLOAT32 wirklich treffen kann (Abb. 1)
Die Abbildung unten zeichnet als Punkte die Werte, die einfache Genauigkeit nach IEEE 754 (FLOAT32) rund um einen Zielwert Ihrer Wahl tatsächlich darstellen kann. Die gestrichelte Linie ist „der gewünschte Dezimalwert“; der gefüllte Punkt fl ist „der nächstgelegene Teilstrich, den FLOAT32 halten kann“. Wo immer die gestrichelte Linie die Punkte verfehlt, ist genau dieses Verfehlen der Rundungsfehler.
Abbildung 1 Die Teilstriche, die FLOAT32 wirklich treffen kann (Zahlenstrahl und nächstes fl)
Die Ansicht startet eng (8 ULP je Seite, also acht Teilstrichabstände). Achten Sie auf den Abstand zwischen der gestrichelten Linie (dem gewünschten Wert) und dem blauen Punkt fl (dem nächstgelegenen FLOAT32-Wert). Ziehen Sie den Regler auf, drängen sich die Striche zusammen; ziehen Sie ihn zu, wird der einzelne Abstand wieder sichtbar.
Ideal wäre: Eine Summe ist einfach jeder Betrag, so wie er ist, aufaddiert. In FLOAT rundet jede Addition das Ergebnis erneut.
\[ s_0 = 0, \qquad s_{k+1} = \mathrm{fl}(s_k + x_k) \]
Jedes einzelne \(\mathrm{fl}\) ist klein, aber mit wachsendem \(k\) kann der Abstand zwischen \(s_n\) und der idealen Summe sichtbar werden. So taucht ein Fehler, den auf einem einzelnen Beleg niemand bemerkt, zum ersten Mal in der Monats-SUM auf.
# Das Prinzip (Pseudocode) — x kann von Beleg zu Beleg variieren
s = 0
für jeden Betrag x_k:
s = fl(s + x_k) # jedes Mal auf den nächsten Teilstrich gerundet
# Ideal wäre x_1+…+x_n. s muss damit nicht übereinstimmen.
3. Der kumulierte Fehler, während sich die Belege stapeln (Abb. 2)
Die waagerechte Achse ist die Zahl der Additionen \(n\); die senkrechte der Unterschied zwischen der laufenden FLOAT32-Summe und der idealen Dezimalsumme. Wie in einem echten Laden durchläuft der Betrag \(x_k\) ein zyklisches Muster, statt konstant zu bleiben (der Regler bestimmt den Basisbetrag).
In welche Richtung jede Rundung kippt, hängt davon ab, wo die laufende Summe zwischen zwei Teilstrichen liegt (der Strichabstand, das ULP, ändert sich mit der Größenordnung), und vom nächsten Betrag \(x_k\). Der Fehler ist also nicht dazu verdammt, stur nach oben zu kriechen: Er kann ins Negative ausschlagen, zurückkommen oder zittern. Die breiten Wellen entstehen, wenn die Rundung eine Weile in dieselbe Richtung drängt; das feine Sägezahnmuster kommt daher, dass die Rundungsrichtung mit den wechselnden Beträgen umspringt.
Eines sei betont: Dieses Zappeln ist kein Zufall. Addieren Sie denselben Betrag zur selben laufenden Summe, erhalten Sie jedes Mal dasselbe Ergebnis. Die Beträge in der Abbildung folgen einem festen Multiplikator-Zyklus; zur Laufzeit wird nicht gewürfelt. Es sieht erratisch aus, doch der Mechanismus ist vollständig deterministisch.
Abbildung 2 Der kumulierte Fehler beim fortgesetzten Addieren (laufende FLOAT32-Summe − ideal)
Die Lehre daraus: Ein in einer Buchung unsichtbarer Unterschied wird sichtbar, wenn sich Buchungen ansammeln. Erhöhen Sie \(n_{\max}\) und vergleichen Sie die Größenordnung der End-Abweichung in der Anzeige.
4. Dieselben Belege in FLOAT und in DECIMAL summiert (Abb. 3)
Abb. 2 zeigte die Größe der Abweichung. Jetzt wechseln wir den Blickwinkel und beobachten, wie sich dieselbe Belegspalte in zwei Summen aufspaltet – eine in FLOAT, eine in DECIMAL summiert, mit Formel, Tabelle und Grafik im Gleichschritt. Die Beträge durchlaufen dasselbe Multiplikator-Muster wie in Abb. 2 (nominelle Basis \(0.10\)).
DECIMAL addiert jedes \(x_k\) exakt, dezimal. FLOAT32 schiebt bei jedem Schritt ein \(\mathrm{fl}\) dazwischen.
\[ S_{\mathrm{DECIMAL}}(n) = x_1 + x_2 + \cdots + x_n \]
\[ S_{\mathrm{FLOAT}}(n) = \mathrm{fl}\bigl(\cdots \mathrm{fl}(\mathrm{fl}(0 + \mathrm{fl}(x_1)) + \mathrm{fl}(x_2)) \cdots + \mathrm{fl}(x_n)\bigr) \]
In SQL sieht ein Minimalbeispiel so aus. Dieselbe SUM; nur die Spaltentypen unterscheiden sich.
-- Ein SQL-Minimalbeispiel:
-- die Beträge variieren je Zeile, und doch kann dieselbe SUM je nach Typ auseinanderlaufen
SELECT SUM(amount_float) AS sum_float, -- FLOAT / REAL
SUM(amount_decimal) AS sum_decimal -- z. B. DECIMAL(10,2)
FROM sales;
In Tabelle 1 unten zeigen die ersten Zeilen kaum einen Unterschied; erst in der letzten Zeile, wenn sich die Buchungen gestapelt haben, tritt die Abweichung ans Licht (die DECIMAL-Seite ist die exakte Summe, auf Ganzzahlebene gerechnet).
| Zeile | Beleg (angezeigt) | Laufende Summe FLOAT32 | Laufende Summe DECIMAL | Abweichung (F−D) |
|---|
Abb. 3 zeigt denselben Vorgang wie Tabelle 1, aber mit der DECIMAL-Summe als Grundlinie (Abweichung null). Die senkrechte Achse ist nicht die Summe selbst, sondern SUM aus FLOAT minus SUM aus DECIMAL. Die orange gestrichelte Linie markiert „Abweichung null (deckungsgleich mit DECIMAL)“; die blaue Kurve ist der Verlauf dieser Abweichung. Die Beträge wechseln exakt wie in Abb. 2, also ist das feine Zittern wieder da (und wieder: kein Zufall).
Ihre Intuition, dass „beide Summen nach rechts oben klettern und FLOAT nur ein wenig abdriftet“, ist richtig. Aber die Drift ist im Verhältnis zu den Summen so klein, dass beide Linien in voller Skala zu einer einzigen verschmelzen würden. Deshalb vergrößert die Abbildung nur die Differenz. Wenn die blaue Kurve absackt oder ansteigt, schrumpft nicht die Summe – unterwegs hat die Rundungsrichtung gewechselt und die FLOAT-Summe unter die DECIMAL-Summe gedrückt oder wieder herangezogen.
Abbildung 3 SUM aus FLOAT − SUM aus DECIMAL (Verlauf der Abweichung)
Lesen Sie beide im Doppel: Tabelle 1 = die Summen selbst; Abb. 3 = die Differenz, vergrößert. Die Geldspalte als DECIMAL anzulegen ist die Entscheidung, die diese blaue Drift beseitigt, bevor sie beginnt.
5. Das berühmte 0.1 + 0.2 ≠ 0.3 ist dieselbe Geschichte
Das in den Konsolen der Programmiersprachen endlos diskutierte Beispiel ist genau dasselbe „Lineal mit fehlenden Teilstrichen“. Wir haben diesen Artikel mit davonlaufenden Geldsummen eröffnet; hier zeigt sich derselbe Kern in anderem Kostüm.
\[ \mathrm{fl}(0.1) + \mathrm{fl}(0.2) \;\neq\; \mathrm{fl}(0.3) \quad (\text{in den meisten Umgebungen}) \]
Im JavaScript Ihres Browsers gilt die folgende Gleichheit nicht streng (die Anzeigerundung kann das verschleiern).
0.1 + 0.2 === 0.3 // false 0.1 + 0.2 // 0.30000000000000004
Beachten Sie: JavaScript-Zahlen sind doppelte Genauigkeit (FLOAT64). Deren Teilstriche sind viel feiner als die FLOAT32-Striche aus Abb. 1, aber das Bild ist identisch: 0.1 trifft keinen Strich. Schalten Sie Abb. 1 zwischen 0.1, 0.2 und 0.3 um, und Sie sehen jeden dieser Werte in derselben Ansicht die Striche verfehlen. Die abweichende Monatssumme und dieses Konsolen-Kuriosum sind derselbe fehlende Teilstrich, an zwei verschiedenen Orten betrachtet.
6. Für die Praxis
- Preise, Stückkosten, Steuersätze, Rechnungssummen — DECIMAL. Wurden die Daten bereits in FLOAT gespeichert, holt nachträgliches Runden verlorene Stellen nicht immer zurück.
- Sensordaten, Statistiken, ML-Features — dominiert ohnehin das Mess- oder Modellrauschen, reicht FLOAT/DOUBLE in aller Regel völlig.
- Anzeigeformatierung (
toFixedund Co.) und der Typ, in dem gespeichert und aggregiert wird, sind zwei Paar Schuhe. Eine hübsche Anzeige ändert nichts: Ist die DB-Spalte FLOAT, kann die SUM weiterhin abweichen.
Entscheidungstabellen und CREATE-TABLE-Beispiele stehen in SQL-Datentypen richtig wählen; dieser Artikel konzentriert sich auf den Mechanismus der Abweichung.
Fazit
- FLOAT hält die meisten Dezimalbrüche nur näherungsweise; das ist der Preis für einen riesigen Bereich bei hoher Geschwindigkeit.
- Die Wurzel der Abweichung: So saubere Dezimalwerte wie 0.1 treffen die binären Teilstriche nicht.
- Eine Rundung ist winzig; eine Rundung bei jeder Addition wird in der Summe sichtbar.
- Dieselben Belege können in FLOAT und DECIMAL verschiedene SUMs ergeben (Abb. 3).
- Muss das Geld exakt stimmen, nehmen Sie DECIMAL. Der FLOAT-Fehler ist konstruktionsbedingt; keine Fehlersuche der Welt schafft ihn weg.
FAQ
Q1. Wie groß ist der Fehler konkret?
A. FLOAT32 trägt rund 7 signifikante Dezimalstellen, DOUBLE (FLOAT64) rund 15–16. „Um wie viele Cent es abweicht“ hängt von Größenordnungen, Stückzahlen und der Rechenreihenfolge ab – die praktische Antwort ist, in Abb. 2 am Zähler zu ziehen und ein Gefühl dafür zu entwickeln.
Q2. Ist DOUBLE dann für Geld akzeptabel?
A. Nein. Die zusätzlichen Stellen machen die Abweichung nur schwerer sichtbar; sie garantieren nicht, dass Dezimalbeträge exakt dargestellt werden. Für Geld lautet die Antwort DECIMAL.
Q3. Ist das dasselbe Problem wie das 0.1+0.2 der Programmiersprachen?
A. Dieselbe Familie. Das FLOAT von SQL und der Standard-Gleitkommatyp der meisten Sprachen teilen sich die binäre Näherung. Es ist eine Eigenschaft der Darstellung, kein datenbankspezifischer „seltsamer Bug“.
Q4. Gleiche Tabelle, aber die FLOAT-SUM fällt je Lauf leicht unterschiedlich aus. Warum?
A. Gleitkomma-Addition kann ihr Ergebnis ändern, wenn sich die Reihenfolge der Additionen ändert (die Assoziativität gilt nicht streng). Ändert parallele Aggregation oder ein anderer Index die Lesereihenfolge der Zeilen, können die letzten Stellen der SUM bei identischen Daten wackeln. Das ist dasselbe Verhalten „die Rundung hängt von der laufenden Summe ab“ wie in Abb. 2. Eine DECIMAL-SUM stimmt unabhängig von der Reihenfolge überein.
Q5. Wir haben Geld bereits in FLOAT gespeichert. Was nun?
A. Stellen Sie zuerst neue Schreibvorgänge auf DECIMAL um. Die Korrektur der Historie verlangt eine fachliche Entscheidung, welchen Stellen noch zu trauen ist; mechanisches Nachrunden stellt die Wahrheit nicht immer wieder her. Siehe den Abschnitt „Geld in FLOAT“ in SQL-Datentypen richtig wählen.

Schreibe einen Kommentar