Einleitung: Das Rätsel von 0.1 + 0.2
Jeder Programmierer erlebt früher oder später diesen Moment der Verblüffung:
| |
Wie kann ein moderner Prozessor, der Billionen Rechenoperationen pro Sekunde meistert und komplexe neuronale Netze trainiert, an einer simplen Grundschulaufgabe wie $0.1 + 0.2 = 0.3$ scheitern? Dies ist kein Fehler der Programmiersprache. Ob x86-CPU, Apple-Silicon oder Hochleistungs-GPU: Jeder moderne Digitalrechner liefert exakt das Ergebnis 0.30000000000000004.
flowchart TD
subgraph Dec ["Dezimalwelt (Menschliche Intuition)"]
D1["0.1 = 1/10 (Endlicher Dezimalbruch)"]
D2["0.2 = 1/5 (Endlicher Dezimalbruch)"]
D3["0.1 + 0.2 = 0.3 (Exakt)"]
D1 --> D3
D2 --> D3
end
subgraph Bin ["Binäre Hardwarewelt (IEEE 754)"]
B1["0.1 = (0.000110011...)₂\nUnendlicher periodischer Bruch"]
B2["0.2 = (0.001100110...)₂\nUnendlicher periodischer Bruch"]
B3["Abschneiden auf 53 Bit Mantisse & Exponentenabgleich\nIEEE 754 Rundung zur nächsten geraden Zahl"]
B4["Gespeicherter Wert:\n0.3000000000000000444..."]
B1 --> B3
B2 --> B3
B3 --> B4
end
Dec -.->|Digitalisierung| Bin
style Dec fill:#e6f3ff,stroke:#0066cc,stroke-width:2px
style Bin fill:#ffe6e6,stroke:#cc0000,stroke-width:2px
1. Die mathematische Ursache: Periodische Brüche im Binärsystem
Im Dezimalsystem lässt sich der Bruch $\frac{1}{3} = 0.3333\dots$ nicht als endliche Zahl darstellen. Ein Bruch $\frac{P}{Q}$ ist in einer Basis $N$ genau dann endlich darstellbar, wenn alle Primfaktoren von $Q$ auch Primfaktoren von $N$ sind.
- Im Zehnersystem ($10 = 2 \times 5$) führen Nenner mit den Faktoren 2 und 5 zu endlichen Brüchen ($0.1 = \frac{1}{10}$).
- Im Zweiersystem (Basis 2) ist der einzige Primfaktor die 2. Nur Brüche mit $2^k$ im Nenner sind endlich.
Da Register endlich sind, muss die Zahl abgeschnitten werden.
2. Der IEEE 754 Standard und die Addition im Register
Nach IEEE 754 (binary64) wird eine 64-Bit-Gleitkommazahl zerlegt in:
- 1 Vorzeichenbit ($s$)
- 11 Exponentenbits mit Bias 1023 ($e$)
- 52 Mantissenbits ($f$) mit impliziter führender Eins (53 Bit effektive Genauigkeit).
flowchart TD
subgraph Binary64Format ["IEEE 754 Double Precision (64 Bit)"]
direction LR
S["Vorzeichen (Sign)\n1 bit"]
E["Exponent (Biased)\n11 bits (Bias 1023)"]
M["Mantisse (Significand)\n52 bits (53 Bit effektiv)"]
end
S --- E --- M
$$\mathbf{0.30000000000000004440892098500626...}$$Da die direkte Repräsentation von $0.3$ ein abweichendes Bitmuster aufweist, schlägt der Vergleich 0.1 + 0.2 === 0.3 fehl.
3. Typische Fehler und historische Katastrophen
- Auslöschung (Catastrophic Cancellation): Bei der Subtraktion fast gleicher Zahlen gehen signifikante Stellen verloren.
- Absorption: Bei Addition von $10^{16}$ und $1.0$ fällt die kleine Zahl aus den 53 Bits heraus.
- Patriot-Raketen-Fehlschlag (1991): Die ungenaue binäre Repräsentation von 0.1 Sekunden addierte sich über 100 Betriebsstunden zu einer Zeitdrift von 0.34 Sekunden. Das Radar verfehlte die anfliegende Scud-Rakete um 687 Meter – 28 Soldaten starben.
- Ariane-5-Explosion (1996): Überlauf beim Konvertieren einer 64-Bit-Gleitkommazahl in eine 16-Bit-Ganzzahl zerstörte eine 500-Millionen-Dollar-Mission.
4. Best Practices für Software-Entwickler
- Niemals direkter Vergleich (
==): Verwenden Sie stets Maschinengenauigkeit $\epsilon$ (math.isclose). - Finanzmathematik nur mit Festkomma/Decimal: Nutzen Sie Integer-Cents oder Klassen wie
BigDecimal/decimal.Decimal. - Algebraische Umformungen: Vermeiden Sie Subtraktionen fast gleicher Werte durch Rationalisierung.
