Seite 1 von 3
Ein Rundungsproblem
Verfasst: Freitag 26. Januar 2024, 15:28
von Pitwheazle
Mit meiner rechentrainer.app kann man auch das Berechnen von Wechselgeld üben.
Den Betrag des Einkaufes erzeuge ich mit:
Einige Kunden wollen ihr Kleingeld loswerden und bezahlen nicht einfach mit großem Geld, soondern legen noch Kleingeld dazu:
Hin und wieder entsteht hier in meiner entsprechenden Aufgabe ein Rundungsfehler - z.B.:
Du hast für 39,41€ eingekauft und bezahlst mit einem 100€ Schein und 40ct in Münzen.
Wieviel Wechselgeld erhälst du?
Wo ensteht hier der Fehler und wie lässt sich der vermeiden? Die erste Zahl ergibt eine Gleitkommazahl, reicht es, hier einfach nochmals auf zwei Stellen zu runden?
Nachtrag: Runden auf zwei Stellen hilft nicht.
Re: Ein Rundungsproblem
Verfasst: Freitag 26. Januar 2024, 15:58
von geraldfo
Für Geldbeträge musst du statt dem Typ Float die Klasse Decimal verwenden.
LG Gerald
Re: Ein Rundungsproblem
Verfasst: Freitag 26. Januar 2024, 16:12
von noisefloor
Hallo,
das grundsätzliche Problem ist hier die computerbedingte Ungenauigkeit bei floats, siehe z.B.
https://docs.python.org/3/tutorial/floatingpoint.html.
Abhilfe bei Python: das Decimal-Modul, siehe z.B.
https://docs.python.org/3/library/decimal.html, wie schon von @geraldfo gesagt.
Gruß, noisefloor
Re: Ein Rundungsproblem
Verfasst: Freitag 26. Januar 2024, 16:13
von Sirius3
Der Rundungsfehler entsteht bei int. Das rundet immer ab. Benutze round, oder besser, rechne immer mit Cent und rechne nur bei der Ausgabe auf €.
Re: Ein Rundungsproblem
Verfasst: Freitag 26. Januar 2024, 16:50
von Pitwheazle
Sirius3 hat geschrieben: Freitag 26. Januar 2024, 16:13
Der Rundungsfehler entsteht bei int. Das rundet immer ab. Benutze round, oder besser, rechne immer mit Cent und rechne nur bei der Ausgabe auf €.
Das war ja meine ursprüngliche Idee, dass ich zunächst mit int einen Centbetrag erzeuge - das reicht aber wohl nicht. Ich dachte, bei int habe ich das Problem mit dem Ungenauigkeitsproblem nicht, das entsteht ja doch erst bei der Division - oder nicht? (Allerdings sollte doch dann "round) helfen - tut es aber nicht.) Wenn ich alles auf Cent umstelle, müsste ich den ganzen Code umstellen. Vielleicht geht das ja mit Decimal besser?
Ich hatte ja gehofft, dieses Problem zu umgehen, indem ich erstmal eine Ganzzahl (also Cent) erzeuge - nun ja, das hat wohl nicht immer geklappt.
Verstehe ich das richtig, dass ich dazu zunächst einen String erzeugen muss?
Re: Ein Rundungsproblem
Verfasst: Freitag 26. Januar 2024, 17:18
von Dennis89
Hallo,
du musst keinen String erzeugen sondern ein `Decimal`-Objekt:
Code: Alles auswählen
>>> zaehler = Decimal(5)
>>> nenner = Decimal(9)
>>> zaehler / nenner
Decimal('0.5555555555555555555555555556')
Kannst du irgendwie zusammenhängenden Code zeigen, der lauffähig ist und zeigt, wieso das mit dem Centbetrag bei dir nicht funktioniert?
Grüße
Dennis
Re: Ein Rundungsproblem
Verfasst: Freitag 26. Januar 2024, 17:49
von Pitwheazle
Dennis89 hat geschrieben: Freitag 26. Januar 2024, 17:18
Kannst du irgendwie zusammenhängenden Code zeigen, der lauffähig ist und zeigt, wieso das mit dem Centbetrag bei dir nicht funktioniert?
Habe ich noch nie gemacht - geht das so:
Code: Alles auswählen
import random
def format_zahl(wert, stellen=2, trailing_zeros=True):
text = f"{wert:.{stellen}f}".replace(".", ",")
return text.rstrip(",0") if not trailing_zeros and "," in text else text
NOTES = [5, 10, 20, 50, 100]
einkauf =random.randint(5, 5950)/100
start = 0
while True:
if NOTES[start] > einkauf:
break
start += 1
gegeben = (random.choice(NOTES[start:]))
art = "Schein"
kleingeld = int(einkauf*100)%100
text = "Du hast für {}€ eingekauft und bezahlst mit einem {}€ {}"
if kleingeld > 0 and random.random()>0.1:
if kleingeld > 50:
kleingeld -=50
if art == "Schein":
text = text + " und {}ct in Münzen".format(kleingeld)
erg = gegeben - einkauf + (round(kleingeld/100,2))
else:
erg = gegeben - einkauf
text = text + ".<br> Wieviel Wechselgeld erhälst du?"
variable = [format_zahl(einkauf,2),format_zahl(gegeben,0),art,format_zahl(kleingeld/100,2)]
print(text.format(*variable))
Anscheinend habe ich das mit Decimal wiedermal überhaupt nicht verstanden. Ich muss doch nicht etwa:
in
Code: Alles auswählen
cent = decimal(random.randint(5, 5950))
nenner = decimal(100)
einkauf = cent/nenner
aufteilen? Das muss doch einfacher gehen?
Re: Ein Rundungsproblem
Verfasst: Freitag 26. Januar 2024, 18:14
von noisefloor
Hallo,
Das muss doch einfacher gehen?
So?
Code: Alles auswählen
cent = decimal(random.randint(5, 5950))
einkauf = cent/decimal(100)
Wobei das nicht die krasse Vereinfachung ist...
Oder du machst es, wie von @Sirius3 vorgeschlagen: konsequent _bis_zum_Schluss_ mit Cent rechnen (=Integer-Werte) und erst für die Ausgabe in Euro und Cent umrechnen (= geteilt durch 100). Dann wird der 50 Euro Schein z.B. zum 5000 Cent Schein intern beim Rechnen. Wo der Rundungsfehler programmtechnisch herkommt hat Sirius3 dir ja auch gezeigt.
Gruß, noisefloor
Re: Ein Rundungsproblem
Verfasst: Freitag 26. Januar 2024, 18:15
von Pitwheazle
Danke!
Re: Ein Rundungsproblem
Verfasst: Freitag 26. Januar 2024, 19:56
von nezzcarth
Sirius3 hat geschrieben: Freitag 26. Januar 2024, 16:13
Benutze round, oder besser.
Nebenbemerkung zu round: 'round' rundet wissenschaftlich, während in der Schule i.d.R. kaufmännisches Runden gelehrt wird. Dies könnte dann bei denen, die die Software verwenden sollen, zu Verwirrung sorgen. Das 'decimal' Modul schafft aber auch hier Abhilfe, denn neben den genannten Vorteilen kann man dort einstellen, wie gerundet werden soll.
Re: Ein Rundungsproblem
Verfasst: Freitag 26. Januar 2024, 21:59
von Sirius3
Was verstehst Du an "rechnen mit Cent" nicht?
`einkauf` rechnest Du ganz am Anfang von Cent in Euro um! Laß das.
Die while-Schleife sollte eigentlich eine for-Schleife sein,
dann ist es auch egal, wie die NOTES sortiert sind.
Bei gegeben sind zu viele Klammern.
Da art ja immer "Schein" ist, fliegt einiges an Code raus.
Modularisiere Deinen Code auch mehr, also berechne das Kleingeld und bestimme in einem unabhängigen Schritt, wie der Text aussieht.
Wenn Du schon eine Template-Engine hast, warum nutzt Du sie dann nicht?
Code: Alles auswählen
import random
from django.template import Template, Context, Engine, Library
register = Library()
@register.filter
def format_zahl(wert, stellen=2, trailing_zeros=True):
text = f"{wert:.{stellen}f}".replace(".", ",")
return text.rstrip(",0") if not trailing_zeros and "," in text else text
engine = Engine()
engine.template_builtins.append(register)
TEMPLATE_EINKAUF = engine.from_string(
"Du hast für {{einkauf|format_zahl:2}}€ eingekauft und bezahlst mit einem {{gegeben|format_zahl:0}}€ Schein"
"{% if kleingeld > 0 %} und {{kleingeld|format_zahl:0}}ct in Münzen{% endif %}.<br>"
"Wieviel Wechselgeld erhälst du?"
)
NOTES = [5, 10, 20, 50, 100]
def aufgabe():
einkauf =random.randint(5, 5950)
gegeben = random.choice([note for note in NOTES if note * 100 > einkauf])
kleingeld = einkauf % 50 if random.random() > 0.1 else 0
ergebnis = gegeben * 100 + kleingeld - einkauf
context = Context(dict(
einkauf=einkauf / 100,
gegeben=gegeben,
kleingeld=kleingeld
))
print(TEMPLATE_EINKAUF.render(context))
print(ergebnis)
Re: Ein Rundungsproblem
Verfasst: Freitag 26. Januar 2024, 22:00
von Pitwheazle
nezzcarth hat geschrieben: Freitag 26. Januar 2024, 19:56
Nebenbemerkung zu round: 'round' rundet wissenschaftlich, während in der Schule i.d.R. kaufmännisches Runden gelehrt wird.
Ui - da gibt es einen Unterschied?
Re: Ein Rundungsproblem
Verfasst: Freitag 26. Januar 2024, 22:15
von Sirius3
Re: Ein Rundungsproblem
Verfasst: Freitag 26. Januar 2024, 22:48
von Pitwheazle
Was verstehst Du an "rechnen mit Cent" nicht?
`einkauf` rechnest Du ganz am Anfang von Cent in Euro um! Laß das.
Ich hatte diese Vorschlag schon verstanden - der gepostete Code bezog sich nicht auf den Vorschlag alles in Cent zu berechnen. Ich hätte es halt gerne auch so gelöst. Möglicherweise taucht das Problem ja an anderer Stelle wieder auf.
Die while-Schleife sollte eigentlich eine for-Schleife sein,
Die ist noch von @whitie Urform, die er mir netterweise zusammengebaut hat - da habe ich mir keine Gedanken drüber gemacht.
Bei gegeben sind zu viele Klammern.
Da art ja immer "Schein" ist, fliegt einiges an Code raus.
Das liegt daran, dass ich den Code zum Posten vereinfacht habe, im Original geibt es auch noch 2€ Münzen.
Danke für den Code. Ich fürchte nur, diese Art Code komplex zusammenzufassen werde ich nie können, ich brauche es mehr linear - sei bitte nachsichtig.
Re: Ein Rundungsproblem
Verfasst: Freitag 26. Januar 2024, 22:55
von Pitwheazle
Wegen deartiger Beschreibungen, habe ich mein Mathestudium beendet und meinen Bronstein/Semendjajev verkauft. Einige Probleme mit dem Runden sind mir schon bekannt, aber auch der untere Teil dieses Wikipediaartikels überfordert mich und ich habe die größte Hochachtung für Leute die das lesen können - ich gehöre leider nicht dazu und muss mich wohl auch leider damit abfinden.
Re: Ein Rundungsproblem
Verfasst: Freitag 26. Januar 2024, 23:37
von nezzcarth
@Pitwheazle: Beim wissenschaftlichen Runden ("symmetrisches Runden" im verlinkten Artikel genannt) wird bei der Fünf zur nächsten geraden Zahl gerundet statt wie beim kaufmännischen Runden pauschal aufzurunden.
Code: Alles auswählen
In [1]: round(1.125, 2)
Out[1]: 1.12
In [2]: round(1.135, 2)
Out[2]: 1.14
Die genannten Probleme mit Floats können zu weiteren Abweichungen vom erwarteten Ergebnis führen.
Re: Ein Rundungsproblem
Verfasst: Freitag 26. Januar 2024, 23:46
von Pitwheazle
Das Problem mit der Fünf war mir bekannt. Ich habe mit den Schülern auch die Auswirkung besprochen, was passiert, wenn man z.B. 0,445 mehrmals nachinander rundet - den Rest des Wikipediatextes schaffe icht trotzdem nicht.. Kannst du mir möglicherweise an meinem Problem explizit erklären, wie der Fehler zustande kommt? - ich sehe hier den Zusammenhang nicht. Ich bin mir des Problems mit der Ungenauigkeit bei Floatzahlen schon bewußt und habe sie an anderer Stelle in den Griff bekommen, kann aber hier bei "int(einkauf*100)%100" das aber nicht nachvollziehen. ich könnte das natürlich mit dem Vorschlag mit der Umstallung auf Cent wegbekommen, habe es dann aber immer noch nicht verstanden.
Re: Ein Rundungsproblem
Verfasst: Samstag 27. Januar 2024, 07:41
von Sirius3
Ich hatte es ja schon angesprochen. int rundet immer ab, round rundet richtig.
Da 100 keine Zweierpotenz ist, kommt es bei der Division zu Ungenauigkeiten, so dass es sein kann, dass das Ergebnis etwas kleiner ist, als der exakte Wert.
Code: Alles auswählen
einkauf = 4091 /100
print(f"{einkauf:.20f}")
# 40.90999999999999658939
print(f"{einkauf * 100:.20f}")
4090.99999999999954525265
Und das führt beim Abrunden zum beobachteten Fehler.
Re: Ein Rundungsproblem
Verfasst: Montag 29. Januar 2024, 09:28
von Kebap
Pitwheazle hat geschrieben: Freitag 26. Januar 2024, 22:55
Wegen deartiger Beschreibungen, habe ich mein Mathestudium beendet und meinen Bronstein/Semendjajev verkauft.
Die ersten paar Kapitel reichen völlig aus. Die Herleitung ist erstmal irrelevant. Wichtig ist nur: Der Computer rundet tlw. anders, als man das von Schülern erwartet, nämlich nicht immer bei 5 auf.
Re: Ein Rundungsproblem
Verfasst: Montag 29. Januar 2024, 14:16
von Qubit
nezzcarth hat geschrieben: Freitag 26. Januar 2024, 19:56
Nebenbemerkung zu round: 'round' rundet wissenschaftlich, während in der Schule i.d.R. kaufmännisches Runden gelehrt wird. Dies könnte dann bei denen, die die Software verwenden sollen, zu Verwirrung sorgen.
Aber das macht "round" auch nicht ganz korrekt..
Code: Alles auswählen
>>> print(round(2.35,1))
2.4
>>> print(round(2.635,2))
2.63
Das zweite Ergebnis sollte laut Regel 3 (Wikipedia) dann "2.64" sein.