Zum Inhalt springen
aviral gupta

// A4.4 · ca. 35 Min. · Vertiefung

Gleitkomma, Decimal und Brüche

Nach dieser Lektion können Sie erklären, warum 0.1 + 0.2 != 0.3 ist, floats mit math.isclose vergleichen, Geldbeträge mit Decimal aus Strings und quantize exakt berechnen und Fraction für exakte Verhältnisse nutzen.

Lektion 4 von 5 in A4 Leistung und Zahlen

Danach können Sie

  • Darstellungsfehler erklären und floats mit math.isclose vergleichen, auch nahe null
  • Mit decimal.Decimal aus Strings exakt rechnen, mit quantize runden und die Genauigkeit in einem Kontext setzen
  • fractions.Fraction für exakte rationale Arithmetik nutzen und zwischen float, Decimal und Fraction wählen
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen aus Lektion B4.4 (Mengen): Was davon kann Element einer Menge sein?

  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie voraus, bevor Sie weiterlesen: Was gibt das aus?

    print(0.1 + 0.2 == 0.3, 0.1 + 0.2)
  3. Üben · Aufgabe 3 von 7

    leftover ist 5.551115123125783e-17, also Rundungsrauschen. Setzen Sie das Schlüsselwort ein, damit der Vergleich es als null behandelt.

    result = math.isclose(leftover, 0.0, ____=1e-12)
    result = math.isclose(leftover, 0.0, =1e-12)
  4. Üben · Aufgabe 4 von 7

    Was gibt das aus?

    from decimal import Decimal
    
    print(Decimal("0.1") * 3 == Decimal("0.3"), Decimal(0.1) * 3 == Decimal("0.3"))
  5. Üben · Aufgabe 5 von 7

    Ordnen Sie jedem Ausdruck sein Ergebnis zu.

  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Was gibt das aus?

    print(round(2.675, 2), round(0.5), round(1.5), round(2.5))
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe. Schreiben Sie split(total, people): total ist ein String wie "10.00", das Ergebnis eine Liste von Decimal-Anteilen in Cent, die genau den Gesamtbetrag ergeben. Runden Sie jeden Anteil mit quantize auf Cent ab und verteilen Sie die übrigen Cent einzeln an die ersten Personen. split("10.00", 3) ergibt 3.34, 3.33 und 3.33. Prüfen Sie die Summe mit ==, nicht mit isclose.

    Prüfen Sie Ihr Ergebnis anhand dieser Liste

Selbst programmieren

Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.

Ausgearbeitetes Beispiel

Eine Rechnung, drei Zahlentypen

Drei Rechnungsposten werden auf drei Arten addiert. Mit floats ist die Nettosumme schon leicht daneben. Mit Decimal aus Strings ist sie exakt, und die 19 % Mehrwertsteuer werden einmal, bewusst, auf Cent gerundet; bei einer exakten Hälfte liefern die zwei Rundungsarten verschiedene Cent. Zum Schluss wird die Summe mit Fraction gedrittelt, das Drittel bleibt exakt, bis Sie es runden.

main.py

import math
from decimal import ROUND_HALF_EVEN, ROUND_HALF_UP, Decimal
from fractions import Fraction

CENT = Decimal("0.01")
VAT_RATE = Decimal("0.19")
lines = [("sugar", "0.10", 2), ("milk", "0.70", 1), ("coffee", "2.20", 3)]

# 1. Floats: every price is already a little off, and the errors add up.
net_float = sum(float(price) * quantity for _, price, quantity in lines)
print("float net:", net_float)
print("== 7.5:", net_float == 7.5, "| isclose:", math.isclose(net_float, 7.5))

# 2. Decimal from strings: exact, and rounded once, on purpose.
net = sum((Decimal(price) * quantity for _, price, quantity in lines), Decimal("0"))
vat = net * VAT_RATE
print("net:", net, "| exact VAT:", vat)
print("VAT half up:  ", vat.quantize(CENT, rounding=ROUND_HALF_UP))
print("VAT half even:", vat.quantize(CENT, rounding=ROUND_HALF_EVEN))
total = net + vat.quantize(CENT, rounding=ROUND_HALF_UP)
print("total:", total)

# 3. Fraction: an exact third, where Decimal and float must round.
share = Fraction(total) / 3
print("a third:", share, "~", round(float(share), 4))

Ausführen mit

python main.py

Ausgabe

float net: 7.500000000000001
== 7.5: False | isclose: True
net: 7.50 | exact VAT: 1.4250
VAT half up:   1.43
VAT half even: 1.42
total: 8.93
a third: 893/300 ~ 2.9767
  • Die float-Nettosumme weicht in der 16. Stelle ab, genug, damit == scheitert; isclose akzeptiert sie trotzdem.
  • Die Decimal-Nettosumme behält zwei Stellen, 7.50, weil ihre Eingaben zwei hatten.
  • 1.4250 liegt genau in der Mitte zwischen 1.42 und 1.43: HALF_UP rundet auf, HALF_EVEN zur geraden 2.
  • Fraction(total) wandelt das Decimal exakt um, und 893/300 bleibt exakt bis zum round.
Ändern und ausführen

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Übungen

Übung 1 von 3

Ein exakter Rechnungsbetrag

invoice_total(lines, vat_rate) bekommt Paare (Preis, Menge) mit Preisen als Strings und einen Satz wie "0.19". Die Funktion soll netto plus Mehrwertsteuer als Decimal liefern, die Steuer kaufmännisch auf Cent gerundet: 7.50 netto bei 19 % ergibt 8.93. Der Starter rechnet mit floats und round. Schreiben Sie ihn mit Decimal aus den Strings und quantize um.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    Decimal(price) * quantity für jeden Posten; beginnen Sie sum bei Decimal("0"), damit das Ergebnis ein Decimal bleibt.

  2. Hinweis 2

    Die Steuer ist (net * Decimal(vat_rate)).quantize(CENT, rounding=ROUND_HALF_UP).

  3. Hinweis 3

    Geben Sie net + vat zurück. Übergeben Sie Decimal hier nie einen float: Dessen Fehler käme exakt mit.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

from decimal import ROUND_HALF_UP, Decimal

CENT = Decimal("0.01")


def invoice_total(lines: list[tuple[str, int]], vat_rate: str) -> Decimal:
    """Net total of (price, quantity) lines plus VAT, the VAT rounded half up to cents."""
    net = sum((Decimal(price) * quantity for price, quantity in lines), Decimal("0"))
    vat = (net * Decimal(vat_rate)).quantize(CENT, rounding=ROUND_HALF_UP)
    return net + vat
Auf dem eigenen Computer ausführen

Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.py

from decimal import ROUND_HALF_UP, Decimal

CENT = Decimal("0.01")


def invoice_total(lines: list[tuple[str, int]], vat_rate: str) -> Decimal:
    """Net total of (price, quantity) lines plus VAT, the VAT rounded half up to cents."""
    net = sum(float(price) * quantity for price, quantity in lines)
    return Decimal(round(net * (1 + float(vat_rate)), 2))

test_main.py

from decimal import Decimal

from main import invoice_total


def test_half_up():
    """7.50 netto bei 19 % ergibt Steuer 1.4250, aufgerundet 1.43: 8.93"""
    got = invoice_total([("0.10", 2), ("0.70", 1), ("2.20", 3)], "0.19")
    assert got == Decimal("8.93"), f"Ergebnis {got!r}, erwartet Decimal('8.93')"


def test_returns_decimal_cents():
    """Das Ergebnis ist ein Decimal mit zwei Stellen"""
    got = invoice_total([("19.99", 3)], "0.07")
    assert isinstance(got, Decimal), f"Ergebnis ist ein {type(got).__name__}, erwartet ein Decimal"
    assert str(got) == "64.17", f"Ergebnis {str(got)!r}, erwartet '64.17' (59.97 + 4.20)"


def test_empty_invoice():
    """Keine Posten: 0.00"""
    got = invoice_total([], "0.19")
    assert str(got) == "0.00", f"Ergebnis {str(got)!r}, erwartet '0.00'"

Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.

Programm ausführen:

python main.py

Prüfungen ausführen (learnrun.py muss im selben Ordner liegen):

python learnrun.py test
learnrun.py herunterladen

Übung 2 von 3

Passende Messwerte

readings_match(a, b) soll sagen, ob zwei float-Messwerte bis auf Gleitkommafehler gleich sind. Der Starter nutzt ==, daher passen 0.1 + 0.2 und 0.3 nicht zusammen. Nutzen Sie math.isclose mit der relativen Standardtoleranz und abs_tol=1e-12, damit winzige Reste nahe null zu 0.0 passen, 1e-6 gegen 0.0 aber weiterhin nicht.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    math.isclose(a, b) behandelt 0.1 + 0.2 gegen 0.3 schon mit dem Standardwert rel_tol von 1e-09 richtig.

  2. Hinweis 2

    Nahe null nützt eine relative Toleranz nichts: rel_tol * abs(0.0) ist 0.0. Dafür gibt es abs_tol.

  3. Hinweis 3

    return math.isclose(a, b, abs_tol=1e-12)

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

import math


def readings_match(a: float, b: float) -> bool:
    """True when two sensor readings are equal up to floating-point error."""
    return math.isclose(a, b, abs_tol=1e-12)
Auf dem eigenen Computer ausführen

Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.py

import math


def readings_match(a: float, b: float) -> bool:
    """True when two sensor readings are equal up to floating-point error."""
    return a == b

test_main.py

from main import readings_match


def test_representation_error():
    """0.1 + 0.2 passt zu 0.3"""
    assert readings_match(0.1 + 0.2, 0.3), "0.1 + 0.2 und 0.3 unterscheiden sich nur durch den Darstellungsfehler"


def test_near_zero():
    """Der Rest von 0.1 + 0.2 - 0.3 passt zu 0.0"""
    leftover = 0.1 + 0.2 - 0.3
    assert readings_match(leftover, 0.0), f"{leftover!r} sollte zu 0.0 passen: nahe null hilft nur abs_tol"


def test_real_difference():
    """1.0 und 1.0001 passen nicht zusammen"""
    assert not readings_match(1.0, 1.0001), "1.0 und 1.0001 unterscheiden sich viel stärker als ein Rundungsfehler"


def test_small_but_real():
    """1e-6 passt nicht zu 0.0"""
    assert not readings_match(1e-6, 0.0), "1e-6 ist ein echter Wert, kein Rundungsrauschen: halten Sie abs_tol winzig"

Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.

Programm ausführen:

python main.py

Prüfungen ausführen (learnrun.py muss im selben Ordner liegen):

python learnrun.py test
learnrun.py herunterladen

Übung 3 von 3

Ein exakter Mittelwert

exact_mean(values) bekommt Zahlen als Strings, etwa "1/3" oder "0.25", und soll ihren Mittelwert als exakten Fraction liefern: Der Mittelwert dreier Drittel ist genau 1/3, der von 0.1, 0.2 und 0.3 genau 1/5. Der Starter geht über float und verliert das. Lesen Sie jeden String mit Fraction ein und wandeln Sie nie in float um.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    Fraction("1/3") und Fraction("0.25") sind exakt; erst float(...) bringt den Fehler herein.

  2. Hinweis 2

    Beginnen Sie die Summe bei Fraction(0): sum((Fraction(v) for v in values), Fraction(0)).

  3. Hinweis 3

    Ein Fraction geteilt durch ein int bleibt ein Fraction; teilen Sie also am Ende durch len(values).

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

from fractions import Fraction


def exact_mean(values: list[str]) -> Fraction:
    """The exact mean of values such as "1/3" or "0.25"."""
    return sum((Fraction(v) for v in values), Fraction(0)) / len(values)
Auf dem eigenen Computer ausführen

Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.py

from fractions import Fraction


def exact_mean(values: list[str]) -> Fraction:
    """The exact mean of values such as "1/3" or "0.25"."""
    return Fraction(sum(float(Fraction(v)) for v in values) / len(values))

test_main.py

from fractions import Fraction

from main import exact_mean


def test_thirds():
    """1/3, 1/3 und 1/3 ergeben im Mittel genau 1/3"""
    got = exact_mean(["1/3", "1/3", "1/3"])
    assert got == Fraction(1, 3), f"Ergebnis {got!r}, erwartet Fraction(1, 3)"


def test_decimal_strings():
    """0.1, 0.2 und 0.3 ergeben im Mittel genau 1/5"""
    got = exact_mean(["0.1", "0.2", "0.3"])
    assert got == Fraction(1, 5), f"Ergebnis {got!r}, erwartet Fraction(1, 5): Lesen Sie die Strings ein, nicht floats"


def test_mixed():
    """1/2 und 0.25 ergeben im Mittel 3/8"""
    got = exact_mean(["1/2", "0.25"])
    assert got == Fraction(3, 8), f"Ergebnis {got!r}, erwartet Fraction(3, 8)"

Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.

Programm ausführen:

python main.py

Prüfungen ausführen (learnrun.py muss im selben Ordner liegen):

python learnrun.py test
learnrun.py herunterladen

Häufige Fehler

Einen float zu einem Decimal addieren

from decimal import Decimal

price = Decimal("19.99")
print(price + 0.01)

Was Python ausgibt

TypeError: unsupported operand type(s) for +: 'decimal.Decimal' and 'float'

Warum, und die Lösung

Decimal lehnt floats in Rechnungen ab, weil der binäre Fehler des floats sonst in einen exakten Wert sickern würde. Schreiben Sie auch die Konstante als Decimal: price + Decimal("0.01"). Ein int zu addieren funktioniert, denn ints sind exakt.

Ein Dezimalkomma im String

from decimal import Decimal

price = Decimal("12,50")

Was Python ausgibt

decimal.InvalidOperation: [<class 'decimal.ConversionSyntax'>]

Warum, und die Lösung

Decimal liest Pythons Zahlensyntax mit Dezimalpunkt. Wandeln Sie deutsche Eingaben vorher um, etwa text.replace(".", "").replace(",", ".") für "1.234,50". Prüfen Sie Benutzereingaben und fangen Sie decimal.InvalidOperation ab.

Einen String an quantize übergeben

from decimal import Decimal

print(Decimal("1.005").quantize("0.01"))

Was Python ausgibt

TypeError: conversion from str to Decimal is not supported

Warum, und die Lösung

quantize erwartet den Exponenten als Decimal: amount.quantize(Decimal("0.01")). Legen Sie ihn einmal als Konstante an, etwa CENT = Decimal("0.01"), und übergeben Sie rounding= ausdrücklich, damit die Rundungsregel im Code sichtbar ist.

Python im Browser: Pyodide 314.0.7, MPL-2.0. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

Binäre floats nähern an

Ein float ist ein Binärbruch mit 53 signifikanten Bits. Die meisten Dezimalbrüche, darunter 0.1, haben keine endliche Binärform; Python speichert den nächstgelegenen und zeigt die kürzeste Dezimalzahl, die wieder auf ihn führt. Jede Operation kann einen neuen Rundungsfehler hinzufügen: 0.1 + 0.2 ist 0.30000000000000004. So arbeitet die Hardware, das ist kein Fehler. Vergleichen Sie berechnete floats daher nie mit ==. math.isclose(a, b) erlaubt einen relativen Unterschied von 1e-09; nahe null brauchen Sie zusätzlich abs_tol, denn rel_tol × 0.0 ist 0.0.

Decimal für Geld

decimal.Decimal("0.10") ist exakt zehn Cent. Erzeugen Sie Decimals aus Strings: Decimal(0.1) übernimmt den Fehler des floats exakt, mit allen 55 Stellen. Decimal behält signifikante Nullen, 1.30 + 1.20 ergibt 2.50. Runden Sie bewusst mit quantize: amount.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP). Der Standardkontext rundet ROUND_HALF_EVEN auf 28 signifikante Stellen; mit localcontext(prec=6) ändern Sie das nur für einen Block. Rechnen mit Decimal und float zusammen löst TypeError aus, das hält den Fehler draußen; ein Vergleich löst nichts aus: Decimal("0.1") == 0.1 ist False.

Fraction für exakte Verhältnisse

fractions.Fraction speichert Zähler und Nenner, immer gekürzt, daher ist Fraction(1, 3) * 3 exakt 1, wo Decimal 1/3 runden müsste. Erzeugen Sie Brüche aus ganzen Zahlen oder einem String: Fraction("0.1") ist 1/10, Fraction(0.1) dagegen der exakte Wert des floats, 3602879701896397/36028797018963968. Wählen Sie float für Messwerte und Tempo, Decimal für dezimales Geld mit festen Stellen, Fraction für exakte Verhältnisse, und wandeln Sie nur an den Rändern um.

Quellen

Zuletzt geprüft am 29. September 2026