Ein Rundungsproblem

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Mit meiner rechentrainer.app kann man auch das Berechnen von Wechselgeld üben.
Den Betrag des Einkaufes erzeuge ich mit:

Code: Alles auswählen

einkauf = random.randint(5, 5950)/100
Einige Kunden wollen ihr Kleingeld loswerden und bezahlen nicht einfach mit großem Geld, soondern legen noch Kleingeld dazu:

Code: Alles auswählen

 kleingeld = int(einkauf*100)%100
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.
geraldfo
User
Beiträge: 44
Registriert: Samstag 28. Januar 2023, 20:19
Wohnort: Nähe Wien

Für Geldbeträge musst du statt dem Typ Float die Klasse Decimal verwenden.

LG Gerald
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

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
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Der Rundungsfehler entsteht bei int. Das rundet immer ab. Benutze round, oder besser, rechne immer mit Cent und rechne nur bei der Ausgabe auf €.
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

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?
noisefloor hat geschrieben: Freitag 26. Januar 2024, 16:12 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.
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?
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

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
"When I got the music, I got a place to go" [Rancid, 1993]
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

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:

Code: Alles auswählen

einkauf =random.randint(5, 5950)/100
in

Code: Alles auswählen

cent = decimal(random.randint(5, 5950))
nenner = decimal(100)
einkauf = cent/nenner
aufteilen? Das muss doch einfacher gehen?
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

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
nezzcarth
User
Beiträge: 1636
Registriert: Samstag 16. April 2011, 12:47

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.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

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)
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

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?
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

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.
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Sirius3 hat geschrieben: Freitag 26. Januar 2024, 22:15 Einfach mal lesen: https://de.wikipedia.org/wiki/Rundung
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.
nezzcarth
User
Beiträge: 1636
Registriert: Samstag 16. April 2011, 12:47

@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.
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

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.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

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.
Benutzeravatar
Kebap
User
Beiträge: 687
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

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.
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
Qubit
User
Beiträge: 128
Registriert: Dienstag 7. Oktober 2008, 09:07

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.
Antworten