Ganzzahlige Division von Fließkommazahlen

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.
Antworten
kemc
User
Beiträge: 5
Registriert: Donnerstag 28. Mai 2015, 12:35
Wohnort: Münster

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
Liffi
User
Beiträge: 153
Registriert: Montag 1. Januar 2007, 17:23

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.
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.
Liffi
User
Beiträge: 153
Registriert: Montag 1. Januar 2007, 17:23

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
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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...
Zuletzt geändert von snafu am Dienstag 24. Januar 2017, 10:25, insgesamt 2-mal geändert.
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
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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.
kemc
User
Beiträge: 5
Registriert: Donnerstag 28. Mai 2015, 12:35
Wohnort: Münster

Hey vielen Dank, mit der floordiv-Funktion konnte ich mein Problem lösen :).

Liebe Grüße
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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
Antworten