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:

Code: Alles auswählen

>>> divmod(3,0.1)
(29.0, 0.09999999999999984)
[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