genaues rechnen, modul decimal?

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.
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

Ich möchte möglichst exakte Rechnungen (gerne auf xte Nachkommastelle genau) tätigen.

Mit float Zahlen ist dies ja leider nur begrenzt möglich (1.1+2.2==3.3000000xx).
Deshalb bin ich auf das Modul Decimal gestoßen. Ist das die Lösung? Oder gibt es bessere Möglichkeiten?

Allerdings gilt immernoch Decimal(1.1)+Decimal(2.2)== 3.30000xx.
Erst mit strings ist es richtig: Decimal("1.1")+Decimal("2.2")== 3.3.

bedeutet das also nun, dass meine Rechnungen nun alle wie folgt aussehen muessen?

Code: Alles auswählen

a = 1.1
b = 2.2
c = Decimal(repr(a)) + Decimal(repr(b)) 
?
Ich glaube alternativ, kann man a und b schon direkt bei der Zuweisung in decimal umwandeln, dann braucht man dies in der Rechnung nicht. Aber dennoch ist es ja schon sehr mühsam, besonders, wenn man ein mehrere tausend zeilen langes skript hat, und nun überall alle float zahlen durch decimal ersetzen muss ^^

Gibt es vllt einen einfacheren/besseren Weg?
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Lesen & verstehen: http://floating-point-gui.de/

Danach weisst du auch, dass der Weg aus Floats Strings und danach Decimal zu machen im Allgemeinen falsch sein muss.

`decimal` ist auch nicht beliebig genau, wie du aus der Doku entnehmen kannst, und um ein vielfaches langsamer.

Du solltest aber erst einmal verstehen warum sich Floats so verhalten wie sie es tun.
BlackJack

@Serpens66: Hat man denn so viele Float-Literale im Code? Notfalls kann man die mit regulären Ausdrücken suchen und ersetzen — das kann eigentlich jeder vernünftige Editor, oder man nimmt das `ast`-Modul um `float`-Literale zu finden.

Wobei ich ja erst einmal ausrechnen würde ob `float` tatsächlich zu ungenau ist, für den Einsatzzweck, denn `Decimal` ist deutlich langsamer.
Benutzeravatar
MagBen
User
Beiträge: 799
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

Es hängt davon ab, wozu Du die Rechnung brauchst.
Ist es was aus dem wissenschaftlich-technischen Bereich? Dann wird mit Float gerechnet und eine Fehlerrechnung dazu gemacht.
Ist es was aus dem Finanzbereich? Dann wird die Rechnung oftmals ganzzahlig durchgeführt, als Einheit wird 1/100 Cent genommen. Einige Programmiersprachen haben dafür auch extra einen Datentyp Currency.
a fool with a tool is still a fool, www.magben.de, YouTube
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

cofi hat geschrieben:Lesen & verstehen: http://floating-point-gui.de/

Danach weisst du auch, dass der Weg aus Floats Strings und danach Decimal zu machen im Allgemeinen falsch sein muss.

`decimal` ist auch nicht beliebig genau, wie du aus der Doku entnehmen kannst, und um ein vielfaches langsamer.

Du solltest aber erst einmal verstehen warum sich Floats so verhalten wie sie es tun.
"warum sie sich so verhalten" - reicht es da nicht einfach so wissen, "ist halt so bei computern" ?
Im Link wird heriauf verwiesen, wenn man exakt rechnen will: http://floating-point-gui.de/formats/exact/
aber da steht nicht, wie man die 3 vorgestellten Arten nun verwenden kann...
BlackJack hat geschrieben:@Serpens66: Hat man denn so viele Float-Literale im Code? Notfalls kann man die mit regulären Ausdrücken suchen und ersetzen — das kann eigentlich jeder vernünftige Editor, oder man nimmt das `ast`-Modul um `float`-Literale zu finden.

Wobei ich ja erst einmal ausrechnen würde ob `float` tatsächlich zu ungenau ist, für den Einsatzzweck, denn `Decimal` ist deutlich langsamer.
Naja, so einfach ist das jetzt nicht, alles float in decimal umzuwandeln. Denn ich hab ja nicht überall "float()" stehen, was ich nun infach in decimal umwandeln kann. So habe ich z.b einfach 1.1 da stehen, was ja automatisch float ist. Es wäre also schon einiges an Arbeit, wenn ich alles umwandeln wuerde. Aber vorher werde ich denke ich auch nochmal genauer nachschauen, wie viel mehr Zeit dadurch auf dem Pi gebraucht wird.

bisher habe ich das Problem immer mit round umgangen. In den meisten Fällen ist das auch ausreichend, weshalb ich vermutlich nicht das ganze skript umschreiben muss.
Allerdings habe ich z.b folgende fkt, bei welcher die "bishermenge" zum schluss exakt so gro sein soll, wie die maxmenge (auf 2 kommastellen genau).

Code: Alles auswählen

maxmenge = 1.5
list = [{"menge":0.12,"preis":200.11,"restmenge":0.12},{"menge":0.34,"preis":200.13,"restmenge":0.34},{"menge":0.02,"preis":201.05,"restmenge":0.02},...] # halt noch viele weitere einträge
for entry in list:
    if bishermenge == maxmenge:
        break
    rest = maxmenge - bishermenge
    
    if rest >= entry["restmenge"]: 
        verwendete_Menge = self.abrunder(entry["restmenge"],2)
        entry["restmenge"] = 0 #hier egal, wenn eigentlich noch kleine bruchstücke über sind
    elif rest< entry["restmenge"]:
        verwendete_Menge = self.abrunder(rest,2)
        entry["restmenge"] = entry["restmenge"] - rest
        
    bishermenge += verwendete_Menge
Das Problem hierbei ist nun, dass "rest = maxmenge - bishermenge", potentiell sowas wie 0.27999999 ergeben kann, wenn eigentlich 0.28 rauskommen sollte. Weil ich das nun abrunde (aufrunden darf ich nicht, da ich nicht mehr nehmen darf, als vorhanden), lande ich bei 0.27, was bei den aktuellen größenordnungen schon eine sehr große abweichung und nicht okay ist.

Daher sehe ich an dieser Stelle keinen anderen Weg, als Decimal zu verwenden.
Zuletzt geändert von Serpens66 am Dienstag 13. Oktober 2015, 15:22, insgesamt 1-mal geändert.
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

MagBen hat geschrieben:Es hängt davon ab, wozu Du die Rechnung brauchst.
Ist es was aus dem wissenschaftlich-technischen Bereich? Dann wird mit Float gerechnet und eine Fehlerrechnung dazu gemacht.
Ist es was aus dem Finanzbereich? Dann wird die Rechnung oftmals ganzzahlig durchgeführt, als Einheit wird 1/100 Cent genommen. Einige Programmiersprachen haben dafür auch extra einen Datentyp Currency.
1 anstelle von 0.01 zu verwenden klingt tatsächlich ziemlich sinnvoll... ich werd mal etwas drueber nachdenken, ob ich das so einsetzen und umsetzen kann...
wäre zwar sicher noch mehr arbeit, aber ich könnte auf zeitfressende dinge wie decimal verzichten...
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Datenmengen im Programm selbst zu definieren ist schon einmal nicht gut.
list = [{"menge":0.12,"preis":200.11,"restmenge":0.12},{"menge":0.34,"preis":200.13,"restmenge":0.34},{"menge":0.02,"preis":201.05,"restmenge":0.02},...] # halt noch viele weitere einträge
Wo kommen die Daten her? Warum werden die, wenn es so viele Einträge gibt, im Programmcode hinterlegt, und nicht aus einer externen Quelle gelesen? Wenn man das nämlich tut, ist es gar kein Problem die Daten beim einlesen direkt in den Typ zu konvertieren den man benötigt.

Alternativ, auch wenn es hässlich ist, kann man hier natürlich auch nach der Definition der Liste durch die Einträge iterieren und die Werte konvertieren.
BlackJack

@Serpens66: Wieso ist das einiges an Arbeit? Ich habe gar nicht `float()` erwartet sondern Gleitkommaliterale, die kann man doch relativ einfach mit regulären Ausdrücken suchen und ersetzen kann. Oder halt richtig gezielt, und ohne „false positives“ beispielsweise in Zeichenketten oder Kommentaren mit dem `ast`-Modul.

Bei den Mengen darf man dann halt auch nur mit ganzen Zahlen rechnen. Macht `Decimal` letztendlich intern ja auch.

@sparrow: Nachträglich geht nicht, denn da hat man dann ja schon `float`-Werte und ein 0.1 ist ja bereits ungenau gespeichert. An der Stelle ist das Kind schon im Brunnen.
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

sparrow hat geschrieben:Datenmengen im Programm selbst zu definieren ist schon einmal nicht gut.
list = [{"menge":0.12,"preis":200.11,"restmenge":0.12},{"menge":0.34,"preis":200.13,"restmenge":0.34},{"menge":0.02,"preis":201.05,"restmenge":0.02},...] # halt noch viele weitere einträge
Wo kommen die Daten her? Warum werden die, wenn es so viele Einträge gibt, im Programmcode hinterlegt, und nicht aus einer externen Quelle gelesen? Wenn man das nämlich tut, ist es gar kein Problem die Daten beim einlesen direkt in den Typ zu konvertieren den man benötigt.

Alternativ, auch wenn es hässlich ist, kann man hier natürlich auch nach der Definition der Liste durch die Einträge iterieren und die Werte konvertieren.
sry hätte ich vllt dazu erwähnen muessen, woher die kommen ^^
Das sind Daten, welche ich via API abfrage. In der API antwort sind nur menge und Preis drin. In meiner liste speichere ich dann zusätzlich dazu halt noch den restmengen eintrag, damit ich immer weiß, wieviel davon noch verwendet werden kann.
Also ja, man kann vermutlich direkt beim auslesen und eintragen in meine variable den Typ konvertieren. Das trifft so ziemlich auf alle zahlen zu richtig.
Das Beispiel sollte in erster Linie zeigen, dass an dieser Stelle meiner meinung nach Decimal-Zahlen benötigt werden. Also stimmst du mir da zu?

@BlackJack:
sparrow hat schon recht, die allermeisten verwendeten Zahlen werden irgendwo ausgelesen und dann in meine liste eingefügt. Dh. an dieser stelle kann ich ansetzen und direkt in decimal umwandeln.
Allerdings, wenn das wirklich länger dauert, ist es ja vllt doch sinnvoller, nur an kritischen stellen decimal zu nehmen und nicht überall...

also fasse ich zusammen:
1) Wenn Decimal überall verwendet werden soll, dann am besten gleich beim auslesen usw in decimal umwandeln.
2) Ansonsten decimal entweder nur an den kritischen Stellen verwenden, oder
3) direkt auf ganze Zahlen umsteigen. (wobei ich dort dann ja auch, wenn ich mengen wie 1.349349 auslese, diese ^100 rechnen und dann auf ganze zahlen runden muss... oder nicht? Wo wir dann wieder bei der rundungsproblematk wären und ich zumindest an dieser stelle decimal verwenden sollte... ?
BlackJack

Wenn Du eine Menge wie 1.349349 ausliest, dann weisst Du doch schon gar nicht mehr ob die genau ist oder wie genau die tatsächlich ist. Da hast Du doch im Grunde schon verloren. Ich denke Du machst Dir zu viele unnötige Gedanken und begehst halt den Fehler Gleitkommazahlen auf exakte gleich/ungleicheit zu vergleichen. Das geht halt nicht. Da vegleicht man üblicherweise den absoluten Unterschied mit einem ”epsilon”-Wert und schaut ob die beiden Zahlen nah genug beieinander liegen um als gleich durchzugehen oder nicht.
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

BlackJack hat geschrieben:Wenn Du eine Menge wie 1.349349 ausliest, dann weisst Du doch schon gar nicht mehr ob die genau ist oder wie genau die tatsächlich ist. Da hast Du doch im Grunde schon verloren. Ich denke Du machst Dir zu viele unnötige Gedanken und begehst halt den Fehler Gleitkommazahlen auf exakte gleich/ungleicheit zu vergleichen. Das geht halt nicht. Da vegleicht man üblicherweise den absoluten Unterschied mit einem ”epsilon”-Wert und schaut ob die beiden Zahlen nah genug beieinander liegen um als gleich durchzugehen oder nicht.
in der API antwort sind die Zahlen ja noch als string, glaub ich.
Dh. doch ich kann sie direkt in decimal umwandeln, um dem problem vorzubeugen, oder nicht?

Wie wuerde so eine kontrolle, ob sie nah genug aneinander sind, denn aussehen? Die bishermenge muss am schluss aufjedenfall exakt dieselbe größe haben, wie maxmenge (kann man ja, wenn nah genug dran, einfach gleichsetzen) und sie darf keinesfalls mehr verwenden, als die restmenge es zulässt. Aber auch nicht zu wenig. Also wenn 0.28 moeglich sind, sollten eben nicht 0.27 verwendet werden (weil aufgrund der float problematik 0.2799999 rauskommt). Aber wenn nur 0.279 tatsächlich moeglich sind, dann muss schon 0.27 verwendet werden und die übrigen 0.009 können vernachlässigt werden.
Benutzeravatar
MagBen
User
Beiträge: 799
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

Serpens66 hat geschrieben:1 anstelle von 0.01 zu verwenden klingt tatsächlich ziemlich sinnvoll... ich werd mal etwas drueber nachdenken, ob ich das so einsetzen und umsetzen kann...
wäre zwar sicher noch mehr arbeit, aber ich könnte auf zeitfressende dinge wie decimal verzichten...
Wenn das ganze sowas wie eine Wage mit Kasse werden soll, dann hast Du wegen gesetzlicher Rahmenbedingungen auch nicht allzuviel Gestaltungsspielraum. Da ist alles ziemlich detailliert geregelt, was mit welcher Genauigkeit und mit wievielen Nachkommastellen gerechnet werden soll.
a fool with a tool is still a fool, www.magben.de, YouTube
BlackJack

@Serpens66: Die API muss die Zahlen ja auch irgendwoher haben, die Frage ist ob da überhaupt schon Wert auf solche Genauigkeit gelegt wurde. Falls die schon mit normalen Gleitkommaoperationen zustande gekommen sind, dann sind die ja potentiell schon ”falsch”, da macht es wenig Sinn es mit der Genauigkeit nun zu übertreiben, die ja durch die Ausgangszahlen eh schon nicht gegeben sein muss.

Vergleich von `a` und `b` auf Gleichheit geht mit ``abs(a - b) < epsilon``. Das `epsilon` muss man halt entsprechend klein/gross festlegen. Muss man halt abschätzen oder tatsächlich ausrechenen wie gross die Fehler bei den Rechnungen werden können.
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

BlackJack hat geschrieben:@Serpens66: Die API muss die Zahlen ja auch irgendwoher haben, die Frage ist ob da überhaupt schon Wert auf solche Genauigkeit gelegt wurde. Falls die schon mit normalen Gleitkommaoperationen zustande gekommen sind, dann sind die ja potentiell schon ”falsch”, da macht es wenig Sinn es mit der Genauigkeit nun zu übertreiben, die ja durch die Ausgangszahlen eh schon nicht gegeben sein muss.

Vergleich von `a` und `b` auf Gleichheit geht mit ``abs(a - b) < epsilon``. Das `epsilon` muss man halt entsprechend klein/gross festlegen. Muss man halt abschätzen oder tatsächlich ausrechenen wie gross die Fehler bei den Rechnungen werden können.
die zahlen aus der API sind exakt ;)

Hm... das mit dem epsilon ist also quasi das, was ich schon mal als notlösung drin hatte :D hatte

Code: Alles auswählen

if bishermenge >= maxmenge-0.009999:  # edit, war vorher verkehrt herum
    bishermenge=maxmenge  # so immernoch nicht ganz korrekt weil so evtl mehr verwendet wird, als erlaubt, aber so in der art war meine "lösung"
    break 
stehen, was ja quasi dasselbe ist, nur dass ich den spielraum in eine richtung begrenze.
Aber das ist selbst mir zu unschön und unsauber :D

gut, dann werd ich wohl bei decimal(repr(a)) + decimal(repr(b)) bleiben... Und da ich ja nicht überall alles in decimal haben will, muss ich das ergebnis in float umwandeln, damit es mit den anderen float werten kompatibel ist... Wodurch dann letzlich eine einfache Rechnung wie c = a + b zu folgendem wird:

Code: Alles auswählen

c = float(Decimal(repr(a)) + Decimal(repr(b)))
besser gehts also scheinbar nicht =/
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Serpens66 hat geschrieben:die zahlen aus der API sind exakt ;)
Das ist völliger Quatsch, weil es mit der Kommanotation nicht mal mehr für die meisten rationalen Zahlen klappt. Das Problem ist hier nicht der Rechner, sondern die Möglichkeiten/Genauigkeit der gewählten Repräsentation. Das hast Du schon, wenn Du mit Bleistift ein Drittel ausschreiben willst. Oder anders gesagt - damit die Werte dort exakt wären, müssten die APIs die Bildungsvorschrift mitliefern.
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

jerch hat geschrieben:
Serpens66 hat geschrieben:die zahlen aus der API sind exakt ;)
Das ist völliger Quatsch, weil es mit der Kommanotation nicht mal mehr für die meisten rationalen Zahlen klappt. Das Problem ist hier nicht der Rechner, sondern die Möglichkeiten/Genauigkeit der gewählten Repräsentation. Das hast Du schon, wenn Du mit Bleistift ein Drittel ausschreiben willst. Oder anders gesagt - damit die Werte dort exakt wären, müssten die APIs die Bildungsvorschrift mitliefern.
Die API werte sind deshalb exakt, weil sie keine Brüche oderso zurückgibt. Alle Zahlen die dort Verwendung finden, haben maximal 8 nachkommastellen. Andere Zahlen können erst garnicht generiert werden (weil so vorgegeben), weshalb sie also exakt sind (weil es nicht mehr als 8 nachkommastellen gibt).
Aber natürlich hast du recht, dass kein 1/3 usw exakt zurückgegeben werden kann.

aber das ist ja an sich auch völlig schnuppe ^^ es geht ja schließlich nur darum, in meiner vorgestellten for schleife unter den gegebenen bedingungen auf die maxmenge zu kommen. ob die "menge" nun 1.23343 oder 1.232323 ist, ist dabei ziemlich egal, da ich ja ohnehin nur auf 2 stellen genau rechne. Die eigentliche Problematik brauch ich ja glaub ich nicht wiederholen, geht ja aus dem post mit der for schleife hervor: http://www.python-forum.de/viewtopic.ph ... 38#p285138
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Serpens66:
Du hast das Problem nicht verstanden - die API liefert die Zahlen bereit mit einem Fehler. Bei 8 Nachkommastellen sind diese wahrscheinlich mit double precision berechnet, d.h. Du kannst einfach mit floats in Python weiterrechnen (sind auch double). Damit hast Du das gleiche Fehlerniveau.
Sirius3
User
Beiträge: 17746
Registriert: Sonntag 21. Oktober 2012, 17:20

@Serpens66: Deine bisherigen Ausführungen lassen mich glauben, dass Du ein grundsätzliches Verständnisdefizit hast, was physikalische Größen und was Gleitkommarechnungen betrifft. Zuerst das Wichtigste: Decimal löst Dein Verständnisproblem nicht.

Wenn Du aus irgendeiner mysteriösen API den String '0.2752' bekommst, heißt das soviel wie, der wahre Wert liegt irgendwo zwischen 0.27515 und 0.27525. Damit bist Du bei einer Genauigkeit, die jede Gleitkommarechnung mit Leichtigkeit erfüllt. Nehmen wir an, dass Du aus irgendwelchen Gründen diese Zahl mit 10 multiplizierst und dann auf 2 Nachkommastellen abrunden willst. Du hast also irgendwas zwischen 2.7515 und 2.7525, also alles was unter 0.0005 von Deinen 2.752 abweicht ist das selbe. Die richtige Rundungsregel lautet also:

Code: Alles auswählen

epsilon = 0.0005
wert = 2.752
print('Ergebnis: {0:.2f}'.format(math.floor((wert + epsilon) * 100) * 0.01)
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

jerch hat geschrieben:@Serpens66:
Du hast das Problem nicht verstanden - die API liefert die Zahlen bereit mit einem Fehler. Bei 8 Nachkommastellen sind diese wahrscheinlich mit double precision berechnet, d.h. Du kannst einfach mit floats in Python weiterrechnen (sind auch double). Damit hast Du das gleiche Fehlerniveau.
neeee. du hast das problem nicht verstanden ;)
Das Problem ist, dass ich mit 0.14+0.14 als ergebnis 0.0279999 rausbekomme (bzw. evlt durch andere rechenoperationen). Dabei ist es also egal, ob die mengen die über api ausgeben wurden nun eigentlich 0.141146 sind oder 0.141147.

Und nein, die API liefert die Zahlen ja eben nicht mit einem Fehler. Denn diese Zahlen kommen durch Nutzer zustande, die diese Mengen erstellen. Die ERstellug von Mengen sind aber auf 8 nachkommastellen limitiert. D.h. mehr als 8 nachkommastellen kann eine "menge" nicht haben. Daher muss die API nur diese Zahl mit 8 nachkommastellen an mich weitergeben und da sie nur 8 stellen hat, ist sie exakt.
(aber wie im oberen absatz erwähnt, ist das nicht die Problematik, auf die ich hinaus will, selbst wenn die Wertenicht exakt wären, wäre das egal)

@ sirius: das bisher geschriebene sollte auch deinen Post beantworten. Wenn die API '0.2752' liefert, dann ist die menge auch exakt 0.2752

edit:
ich kann durchaus verstehen, dass ihr denkt ich würde mich irren was die genauigkeit der API angeht, ich bin ja sonst auch nicht besonders helle, was programmieren angeht :D aber in diesem Fall ist es so und euch fehlen die genauen infos um was fuer mengen es geht und wie diese zustande kommen.

edit2:
ich verstehe jetzt aber was ihr meint, danke. Wenn man davon ausgeht, dass die API einen Fehler von 0.0005 hat, dann stimmt deine Ausführung schon Sirius. und es wäre also nicht komplett egal, ob die api einen fehler (abweichung) hat.
Aber wie dem auch sei, in diesem Fall haben wir keinene Abweichung :)
Zuletzt geändert von Serpens66 am Dienstag 13. Oktober 2015, 18:05, insgesamt 1-mal geändert.
Sirius3
User
Beiträge: 17746
Registriert: Sonntag 21. Oktober 2012, 17:20

@Serpens66: es kommt halt immer auf das Epsilon an, das Du für Dein Problem geeignet wählen mußt.
Antworten