Aufwärmen · Aufgabe 1 von 7
// 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.
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
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)Ü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)Ü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"))Üben · Aufgabe 5 von 7
Ordnen Sie jedem Ausdruck sein Ergebnis zu.
Denksport · Aufgabe 6 von 7
Knobelaufgabe. Was gibt das aus?
print(round(2.675, 2), round(0.5), round(1.5), round(2.5))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.pyAusgabe
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
Hinweis 1
Decimal(price) * quantity für jeden Posten; beginnen Sie sum bei Decimal("0"), damit das Ergebnis ein Decimal bleibt.
Hinweis 2
Die Steuer ist (net * Decimal(vat_rate)).quantize(CENT, rounding=ROUND_HALF_UP).
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.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.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
Hinweis 1
math.isclose(a, b) behandelt 0.1 + 0.2 gegen 0.3 schon mit dem Standardwert rel_tol von 1e-09 richtig.
Hinweis 2
Nahe null nützt eine relative Toleranz nichts: rel_tol * abs(0.0) ist 0.0. Dafür gibt es abs_tol.
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.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.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
Hinweis 1
Fraction("1/3") und Fraction("0.25") sind exakt; erst float(...) bringt den Fehler herein.
Hinweis 2
Beginnen Sie die Summe bei Fraction(0): sum((Fraction(v) for v in values), Fraction(0)).
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.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.py herunterladenHä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 supportedWarum, 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.