Seite 1 von 1
Ganzzahlige Division von Fließkommazahlen
Verfasst: Montag 23. Januar 2017, 18:04
von kemc
Hey ho,
kann mir jemand erklären, warum sich die ganzzahlige Division so verhält wie im folgenden Codebeispiel dargestellt?
[codebox=pycon file=Unbenannt.txt]
>>> 30 // 1 # hier wie erwartet
30
>>> 3 // 0.1 # warum?
29
[/code]
Mit freundlichen Grüßen
kemc
Re: Ganzzahlige Division von Fließkommazahlen
Verfasst: Montag 23. Januar 2017, 20:12
von Liffi
0.1 lässt sich auf Computern nicht präzise darstellen und ist minimal größer als 0.1

. Dadurch kommt bei der Division dann auch 29 raus und nicht 30.
Re: Ganzzahlige Division von Fließkommazahlen
Verfasst: Montag 23. Januar 2017, 21:05
von BlackJack
@Liffi: Keine überzeugende Erklärung weil:
Code: Alles auswählen
In [8]: '%.60f' % (3 / 0.1)
Out[8]: '30.000000000000000000000000000000000000000000000000000000000000'
Das scheint trotz Gleitkommabeschränkungen recht glatt 30 zu ergeben.
Re: Ganzzahlige Division von Fließkommazahlen
Verfasst: Dienstag 24. Januar 2017, 05:32
von Liffi
Code: Alles auswählen
>>> '%.60f' % (0.1)
'0.100000000000000005551115123125782702118158340454101562500000'
Um mal ein anderes Beispiel zu zitieren( 1//0.05). Siehe auch [1].
So, it seems that Python's floating-point division is internally done with high enough precision to know that 1 / 0.05 (that's the float literal 0.05, not the exact decimal fraction 0.05), is actually less than 20, but the float type in itself is incapable of representing the difference.
'//' scheint sich demnach auch wie divmod zu verhalten:
[1]
https://stackoverflow.com/questions/358 ... ect=1&lq=1
Re: Ganzzahlige Division von Fließkommazahlen
Verfasst: Dienstag 24. Januar 2017, 10:09
von snafu
Man könnte es mit dem fractions-Modul lösen (wenn ich nichts übersehen habe):
Code: Alles auswählen
from fractions import Fraction as F
print(3 // F('0.1'))
Oder gleich als Funktion:
Code: Alles auswählen
from fractions import Fraction
def floordiv(a, b):
return Fraction(str(a)) // Fraction(str(b))
def main():
print('Without fractions:', 3 // 0.1)
print('With fractions:', floordiv(3, 0.1))
if __name__ == '__main__':
main()
Es sollte nur klar sein, dass hier ein entsprechender Overhead erzeugt wird (für beide Operanden eine String-Umwandlung + Erzeugung des Fraction-Objekts). Nur für den Fall, dass das eine Rolle spielt...
Re: Ganzzahlige Division von Fließkommazahlen
Verfasst: Dienstag 24. Januar 2017, 10:16
von BlackJack
Hab gerade gesehen das Hy für `Fraction` Literale hat.
Code: Alles auswählen
=> 1/2
Fraction(1, 2)
=> (// 3 0.1)
29.0
=> (// 3 1/10)
30
Re: Ganzzahlige Division von Fließkommazahlen
Verfasst: Dienstag 24. Januar 2017, 11:03
von snafu
Hier nochmal floordiv etwas optimiert:
Code: Alles auswählen
from fractions import Fraction
def floordiv(a, b):
result = a // b
if result * b != a:
result = Fraction(str(a)) // Fraction(str(b))
return result
Damit entfällt der relativ aufwändige Umweg über das fractions-Modul für Divisionen, die bereits auf "normalem" Wege das korrekte Ergebnis liefern.
Re: Ganzzahlige Division von Fließkommazahlen
Verfasst: Dienstag 24. Januar 2017, 12:13
von kemc
Hey vielen Dank, mit der floordiv-Funktion konnte ich mein Problem lösen

.
Liebe Grüße
Re: Ganzzahlige Division von Fließkommazahlen
Verfasst: Dienstag 24. Januar 2017, 12:35
von snafu
Vielleicht noch als Zusatz: Mit dem decimal-Modul kann man dein Problem ganz ähnlich lösen. Es verhielt sich bei meinen Tests (in IPython mit timeit-Kommando gemessen) deutlich schneller als das fractions-Modul. Den passenden Code dazu habe ich so geschrieben:
Code: Alles auswählen
from decimal import Decimal
def floordiv(a, b):
result = a // b
if result * b != a:
# Dealing with at least one "problematic" float
result = float(Decimal(str(a)) // Decimal(str(b)))
return result
Re: Ganzzahlige Division von Fließkommazahlen
Verfasst: Dienstag 24. Januar 2017, 13:23
von snafu
Wobei das noch stark verbesserungsdürftig ist, denn z.B. bei 10 // 3 passiert ja eigentlich nichts problematisches. Ich war wohl sehr auf das Beispiel fixiert und hatte trotz der Benennung nicht bedacht, dass "result * b != a" auch bei normal darstellbaren Floats und natürlich auch bei Integern vorkommen kann. Der Decimal-Code wird also deutlich öfter durchlaufen als nötig und die Annahme, dass mindestens ein Float enthalten ist, trifft auch nicht immer zu.
Vielleicht geht es noch anders, aber ich habe das jetzt mit divmod() gelöst. Ich gucke einfach, ob der Rest eine Ganzzahl ist.
Code: Alles auswählen
from decimal import Decimal
def floordiv(a, b):
div, mod = divmod(a, b)
if mod != int(mod):
# Dealing with at least one "problematic" float
div = float(Decimal(str(a)) // Decimal(str(b)))
return div