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

@Qubit: Dezimalzahlen können nicht immer exakt durch binäre Floats dargestellt werden, 2.635 ist in Wirklichkeit

Code: Alles auswählen

f"{2.635:.20f}"
# 2.63499999999999978684
Und damit kleiner als 2.635 und wird deshalb abgerundet.
Benutzeravatar
DeaD_EyE
User
Beiträge: 1022
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Kaufmännisches Runden geht auch:

Code: Alles auswählen

from decimal import Decimal, localcontext, ROUND_HALF_UP


def wechselgeld(betrag: str, münzen: list[Decimal]):
    with localcontext(rounding=ROUND_HALF_UP):
        betrag = round(Decimal(betrag), 2)
        münzen = sorted(münzen, reverse=True)
        ergebnis = {}
        for münze in münzen:
            menge = betrag // münze
            betrag -= münze * menge
            ergebnis[münze] = int(menge)
        return ergebnis, betrag


münzen = [Decimal(s) / 100 for s in (1, 2, 5, 10, 20, 50, 100, 200)]
ergebnis, rest = wechselgeld("13.367", münzen)
# wir aufgerundet auf 13.37
summe = sum(value * amount for value, amount in ergebnis.items())
print("Münzen:", münzen)
print("Rest:", rest)
print("Wechselgeld:", ergebnis)
print("Summe:", summe, "Typ:", type(summe))
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Ach, ihr seid zu lieb!
Ich habe das jetzt mit @Sirius3 s Vorschlag mit dem Rechnen mit Cent umgesetzt ... und jetzt auch noch ergänzt, dass der Kunde nicht immer 1 Cent Stücke zur Hand hat sondern 2 Cent Stücke hinlegt.
Qubit
User
Beiträge: 128
Registriert: Dienstag 7. Oktober 2008, 09:07

Sirius3 hat geschrieben: Montag 29. Januar 2024, 14:57 @Qubit: Dezimalzahlen können nicht immer exakt durch binäre Floats dargestellt werden, 2.635 ist in Wirklichkeit

Code: Alles auswählen

f"{2.635:.20f}"
# 2.63499999999999978684
Und damit kleiner als 2.635 und wird deshalb abgerundet.
Jup, wobei ja auch diese Float-Darstelllung so nur max. auf 8 Stellen genau sein sollte.

Man könnte jetzt die "round"-Funktion diesbezüglich selbst heilen, wobei das aber eigentlich implementiert sein sollte, oder?
zB.

Code: Alles auswählen

round_float = lambda number, prec=0: round(round(10**prec*number,1))/10**prec

zahl = 2.635
print(round_float(zahl,2))
print(round_float(zahl,20))
print(round_float(zahl,200))
2.64
2.635
2.635
narpfel
User
Beiträge: 645
Registriert: Freitag 20. Oktober 2017, 16:10

@Qubit: Das ist jetzt aber nicht richtig, weil 2.635 näher an 2.63 als an 2.64 ist.
Qubit
User
Beiträge: 128
Registriert: Dienstag 7. Oktober 2008, 09:07

narpfel hat geschrieben: Montag 29. Januar 2024, 21:31 @Qubit: Das ist jetzt aber nicht richtig, weil 2.635 näher an 2.63 als an 2.64 ist.
Was heisst näher?

Der Abstand ist in den reellen Zahlen R über eine (Standard-) Metrik definiert ("Euklidische Metrik"), und da gilt:

|2.635-2.64| = |2.635-2.63|=0.005

Was heisst falsch?

Es geht um die Rundungsvorgabe für die "round"-Funktion. Diese soll nach ""wissenschaftlicher Rundung" erfolgen.
Das tut sie aber nicht korrekt.
Schau dir am Besten noch mal insbesondere Regel 3 des hier irgendwo verlinken Wikipedia-Artikels an.
Sirius3
User
Beiträge: 17761
Registriert: Sonntag 21. Oktober 2012, 17:20

@qubit: Deine Rundung ist aber falsch, wenn ich die Zahl 2.63499999999999978684 runden möchte, wird fälschlicherweise aufgerundet.

Du hast nur den einen nummerischen Fehler durch einen anderen ersetzt. Zum Glück ist klar definiert, wie gerundet werden sollte, und das ist nicht die Näherungsformel, die Du erfunden hast, weil dessen Fehler zu groß ist.
narpfel
User
Beiträge: 645
Registriert: Freitag 20. Oktober 2017, 16:10

@Qubit: 2.635 ist keine reelle Zahl, sondern ein 64-Bit-IEEE-754-Wert. Und in dieser Darstellung ist 2.635 == 2.6349999999999997868371792719699442386627197265625, und das ist eben klar näher an 2.63 als an 2.64.
Qubit
User
Beiträge: 128
Registriert: Dienstag 7. Oktober 2008, 09:07

Sirius3 hat geschrieben: Montag 29. Januar 2024, 22:26 @qubit: Deine Rundung ist aber falsch, wenn ich die Zahl 2.63499999999999978684 runden möchte, wird fälschlicherweise aufgerundet.

Du hast nur den einen nummerischen Fehler durch einen anderen ersetzt. Zum Glück ist klar definiert, wie gerundet werden sollte, und das ist nicht die Näherungsformel, die Du erfunden hast, weil dessen Fehler zu groß ist.
Verstehe da jetzt leider nicht ganz was du meinst, welche Rundung genau?

Code: Alles auswählen

round_float = lambda number, prec=0: round(round(10**prec*number,1))/10**prec

zahl =  2.63499999999999978684
print(f"{round_float(zahl,19):.19f}")
print(f"{round(zahl,19):.19f}"
2.6349999999999997868
2.6349999999999997868
Qubit
User
Beiträge: 128
Registriert: Dienstag 7. Oktober 2008, 09:07

narpfel hat geschrieben: Montag 29. Januar 2024, 22:26 @Qubit: 2.635 ist keine reelle Zahl, sondern ein 64-Bit-IEEE-754-Wert. Und in dieser Darstellung ist 2.635 == 2.6349999999999997868371792719699442386627197265625, und das ist eben klar näher an 2.63 als an 2.64.
Doch "2.635" ist eine reelle Zahl.
Was du meinst, sind "Maschinenzahlen", also die interne Darstellung dieser Zahl.
Es geht aber um die "wissenschaftliche Rundung" (innerhalb der darstellbaren Genauigkeit).
nezzcarth
User
Beiträge: 1638
Registriert: Samstag 16. April 2011, 12:47

@Qubit: Das Verhalten, das du bemängelst, wird in der Doku von round explizit angesprochen und wie von Sirius3 und narpfel beschrieben erklärt (https://docs.python.org/3/library/functions.html#round). Daher würde ich das nicht als Fehler sehen. Der Begriff "wissenschaftliches Runden" taucht in der Doku übrigens nicht auf; den hatte ich nur angeführt und man sollte sich daran vielleicht nicht aufhalten. :)
Zuletzt geändert von nezzcarth am Montag 29. Januar 2024, 23:40, insgesamt 1-mal geändert.
narpfel
User
Beiträge: 645
Registriert: Freitag 20. Oktober 2017, 16:10

@Qubit: In Python eben nicht. Da gibt es keine reellen Zahlen. Kann es auch gar nicht, weil man im allgemeinen Fall nicht wirklich damit rechnen kann. Unter anderem, weil fast alle reellen Zahlen nicht berechenbar sind (es kann also keine Turing-Maschine (und damit auch kein Python-Programm) geben, das die Zahl berechnet). Außerdem sind fast alle reellen Zahlen nichtmal definierbar.

Im Kontext von Programmiersprachen muss man damit immer mit Untermengen der reellen Zahlen rechnen. Und da ist 2.635 eben eine 64-Bit-IEEE-754-Zahl. Wenn man eine andere Untermenge (mit eventuell anderen arithmetischen Eigenschaften) haben will, gibt es z. B. `decimal.Decimal` für Dezimalzahlen mit beliebiger (endlicher) Präzision oder `fractions.Fraction` für rationale Zahlen.
Qubit
User
Beiträge: 128
Registriert: Dienstag 7. Oktober 2008, 09:07

nezzcarth hat geschrieben: Montag 29. Januar 2024, 23:38 @Qubit: Das Verhalten, das du bemängelst, wird in der Doku von round explizit angesprochen und wie von Sirius3 und narpfel beschrieben erklärt (https://docs.python.org/3/library/functions.html#round). Daher würde ich das nicht als Fehler sehen. Der Begriff "wissenschaftliches Runden" taucht in der Doku übrigens nicht auf; den hatte ich nur angeführt, man sollte sich jedoch nicht dran aufhalten.
Naja, nur weil es "dokumentiert" ist, ist es noch nicht von Fehlerhaftigkeit befreit (die Anforderungen zählen).
Bei der Anforderung des "wissenschaftlichen Rundens" geht es eben nicht um "Maschinenzahlen" sondern "reelle Zahlen" der Anwender.
Oder anders gesagt: ich nutze bestimmt nicht Python, um mir gerade über die interne Darstellung von Zahlen Gedanken zu machen. :D
Es ist zwar Schade, dass die "round"-Funktion versagt, aber man sollte die Grenzen und Alternativen kennen.
Eben so ein Workaround von mir (schlecht) oder saubere Implementierungen wie "Decimal" (gut)..
Benutzeravatar
Dennis89
User
Beiträge: 1158
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

`round` würde versagen, wenn sich `round` anders verhalten würde als in der Doku beschrieben. Da aber beschrieben ist, was `round` kann und wie es sich in bestimmten Fällen verhält, ist es dann Anwendersache, die für seine Ansprüche richtige Funktion auszusuchen.


Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Qubit
User
Beiträge: 128
Registriert: Dienstag 7. Oktober 2008, 09:07

narpfel hat geschrieben: Montag 29. Januar 2024, 23:39 @Qubit: In Python eben nicht. Da gibt es keine reellen Zahlen. Kann es auch gar nicht, weil man im allgemeinen Fall nicht wirklich damit rechnen kann. Unter anderem, weil fast alle reellen Zahlen nicht berechenbar sind (es kann also keine Turing-Maschine (und damit auch kein Python-Programm) geben, das die Zahl berechnet). Außerdem sind fast alle reellen Zahlen nichtmal definierbar.

Im Kontext von Programmiersprachen muss man damit immer mit Untermengen der reellen Zahlen rechnen. Und da ist 2.635 eben eine 64-Bit-IEEE-754-Zahl. Wenn man eine andere Untermenge (mit eventuell anderen arithmetischen Eigenschaften) haben will, gibt es z. B. `decimal.Decimal` für Dezimalzahlen mit beliebiger (endlicher) Präzision oder `fractions.Fraction` für rationale Zahlen.
Sorry, ich weiss nicht genau, was du mir erklären willst?
Dass man mit Computern nicht richtig runden kann?
Das glaube ich zwar nicht, aber du vermischt hier für mich einiges was man auseinanderhalten sollte..
Qubit
User
Beiträge: 128
Registriert: Dienstag 7. Oktober 2008, 09:07

Dennis89 hat geschrieben: Montag 29. Januar 2024, 23:53 `round` würde versagen, wenn sich `round` anders verhalten würde als in der Doku beschrieben. Da aber beschrieben ist, was `round` kann und wie es sich in bestimmten Fällen verhält, ist es dann Anwendersache, die für seine Ansprüche richtige Funktion auszusuchen.
Das kann man natürlich so sehen.
Besser wäre es aber, wenn die "round"-Funktion einfach richtig nach "wissenschaftlicher Rundung" funktionieren würde.
Wie gesagt, Dokumentation ist gut, richtige Funktion aber besser :D
narpfel
User
Beiträge: 645
Registriert: Freitag 20. Oktober 2017, 16:10

@Qubit: Wie willst du einen Datentyp für reelle Zahlen definieren, wenn fast alle reellen Zahlen nicht berechenbar sind? Also wirklich für reelle Zahlen und nicht für irgendeine Untermenge? Wenn das nicht geht, dann ja, man kann mit Computern nicht richtig runden. Unter anderem, weil man fast alle Zahlen nichtmal darstellen kann.

Die reelle Zahl 2,635 ist nicht exakt als `float` darstellbar. Mit dem Datentyp `float` ist es also prinzipiell unmöglich, sie „korrekt“ (nach deiner Definition) zu runden. Weil 2,635 eben keine `float`-Zahl ist.

Ist `math.sqrt` auch inkorrekt?

Code: Alles auswählen

In [27]: math.sqrt(2) ** 2
Out[27]: 2.0000000000000004

In [28]: math.sqrt(2) ** 2 == 2.0
Out[28]: False
Qubit
User
Beiträge: 128
Registriert: Dienstag 7. Oktober 2008, 09:07

narpfel hat geschrieben: Dienstag 30. Januar 2024, 00:22 @Qubit: Wie willst du einen Datentyp für reelle Zahlen definieren, wenn fast alle reellen Zahlen nicht berechenbar sind? Also wirklich für reelle Zahlen und nicht für irgendeine Untermenge? Wenn das nicht geht, dann ja, man kann mit Computern nicht richtig runden. Unter anderem, weil man fast alle Zahlen nichtmal darstellen kann.

Die reelle Zahl 2,635 ist nicht exakt als `float` darstellbar. Mit dem Datentyp `float` ist es also prinzipiell unmöglich, sie „korrekt“ (nach deiner Definition) zu runden. Weil 2,635 eben keine `float`-Zahl ist.

Ist `math.sqrt` auch inkorrekt?

Code: Alles auswählen

In [27]: math.sqrt(2) ** 2
Out[27]: 2.0000000000000004

In [28]: math.sqrt(2) ** 2 == 2.0
Out[28]: False
Hm, ich bin ehrlich gesagt im Zweifel, ob du wirklich Ahnung hast, worüber du sprichst..
Ad
1) Natürlich lässt sich jede reelle Zahl beliebig genau (nach Vorgabe) berechnen.
Dazu kannst du z.B. Intervallschachtelungen oder sogen. "Dedekindsche Schnitte" verwenden..

2) Das "Float-Number-" Problem ist genau eines der "internen Präzision". Innerhalb dereren Genauigkeit kann man "reelle Zahlen" auch exakt darstellen.

3) Natürlich ist dein Beispiel des Quadrats der Wurzel inkorrekt, aber nicht innerhalb der Genauigkeit..
Qubit
User
Beiträge: 128
Registriert: Dienstag 7. Oktober 2008, 09:07

narpfel hat geschrieben: Dienstag 30. Januar 2024, 00:22 @Qubit: Wie willst du einen Datentyp für reelle Zahlen definieren, wenn fast alle reellen Zahlen nicht berechenbar sind?
Sorry, ich glaube, wie sprechen auch einander vorbei..

Unstrittig ist, dass Computer mit Maschinenzahlen rechnen.
Und Maschinenzahlen sind keine Dezimalzahlen der Mathematik (zB. kein "Körper" wie rationale oder reelle Zahlen).
Aber diese Maschinenzahlen sind interne Darstellungen dieser Dezimalzahlen.
Und im Kontext eines Algorithmus (wie zB. der der "wissenschaftlichen Rundung"), lassen sich so zwei Maschinenzahlen (als interne Darstellung von Dezimalzahlen) innerhalb der Genauigkeit(!) ineinander überführen,
also hier zB:

Code: Alles auswählen

von
>>> f"{2.635:.20f}"
'2.63499999999999978684'

mit Rundungsalgorithmus

nach
>>> f"{2.64:.20f}"
'2.64000000000000012434'
Aber der (nicht ganz korrekte) Algorithmus der "round"-Funktion liefert hier als Erghebnis

Code: Alles auswählen

>>> f"{2.63:.20f}"
'2.62999999999999989342'
Benutzeravatar
__blackjack__
User
Beiträge: 13123
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Qubit: Du zeigst hier immer Dezimaldarstellungen der Zahlen, intern werden die aber Binär repräsentiert und auch die Operationen erfolgen auf dieser Binärdarstellung. Du müsstest also schon zeigen das auf Binärebene falsch gerundet wird und das es tatsächlich besser ginge, wobei dabei auch bedacht werden muss, dass viele Schritte nicht in Software gemacht werden, sondern in Hardware, und an *den* Algorithmen kann man nichts ändern, es sei denn man will den Vorteil von einer schnelle(re)n Verarbeitung in der Hardware verlieren. → Das will keiner.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten