web-dev-qa-db-de.com

Python modulo on floats

Kann jemand erklären, wie der Modulo-Operator in Python funktioniert? Ich kann nicht verstehen, warum 3.5 % 0.1 = 0.1.

34
beruic

Tatsächlich ist es nicht wahr, dass 3.5 % 0.10.1 ist. Das können Sie ganz einfach testen:

>>> print(3.5 % 0.1)
0.1
>>> print(3.5 % 0.1 == 0.1)
False

Tatsächlich ist 3.5 % 0.1 auf den meisten Systemen 0.099999999999999811. In einigen Versionen von Python ist str(0.099999999999999811)0.1:

>>> 3.5 % 0.1
0.099999999999999811
>>> repr(3.5 % 0.1)
'0.099999999999999811'
>>> str(3.5 % 0.1)
'0.1'

Sie fragen sich wahrscheinlich, warum 3.5 % 0.10.099999999999999811 anstelle von 0.0 ist. Das liegt an den üblichen Gleitkomma-Rundungsproblemen. Wenn Sie nicht gelesen haben Was jeder Informatiker über Gleitkomma-Arithmetik wissen sollte , sollten Sie - oder zumindest die kurze Wikipedia Zusammenfassung dieses speziellen Themas.

Beachten Sie auch, dass 3.5/0.1 nicht 34 ist, sondern 35. Also ist 3.5/0.1 * 0.1 + 3.5%0.13.5999999999999996, was nicht einmal nahe an 3.5 liegt . Dies ist ziemlich grundlegend für die Definition des Moduls, und es ist in Python und fast jeder anderen Programmiersprache falsch.

Aber Python 3 kommt dort zur Rettung. Die meisten Leute, die über // Bescheid wissen, wissen, dass dies die Art und Weise ist, wie Sie eine "Ganzzahldivision" zwischen ganzen Zahlen durchführen. Sie wissen jedoch nicht, wie Sie eine modulkompatible Division zwischen irgendwelchen durchführen Typen. 3.5//0.1 ist 34.0, also 3.5//0.1 * 0.1 + 3.5%0.1 ist (zumindest innerhalb eines kleinen Rundungsfehlers von) 3.5. Dies wurde auf 2.x zurückportiert, sodass Sie sich (abhängig von Ihrer genauen Version und Plattform) möglicherweise darauf verlassen können. Andernfalls können Sie die Funktion divmod(3.5, 0.1) verwenden, die (innerhalb eines Rundungsfehlers) (34.0, 0.09999999999999981) bis in den Nebel der Zeit zurückgibt. Natürlich haben Sie immer noch erwartet, dass dies (35.0, 0.0) ist, nicht (34.0, almost-0.1), aber Sie können dies aufgrund von Rundungsfehlern nicht haben.

Wenn Sie nach einer schnellen Lösung suchen, sollten Sie den Typ Decimal verwenden:

>>> from decimal import Decimal
>>> Decimal('3.5') % Decimal('0.1')
Decimal('0.0')
>>> print(Decimal('3.5') % Decimal('0.1'))
0.0
>>> (Decimal(7)/2) % (Decimal(1)/10)
Decimal('0.0')

Dies ist kein magisches Allheilmittel - Sie müssen sich beispielsweise immer noch mit Rundungsfehlern befassen, wenn der genaue Wert einer Operation in Basis 10 nicht endgültig darstellbar ist - aber die Rundungsfehler stimmen besser mit den Erwartungen der menschlichen Intuition überein problematisch sein. (Es gibt auch Vorteile von Decimal gegenüber float, da Sie explizite Präzisionen angeben, signifikante Ziffern nachverfolgen usw., und dass dies in allen Python tatsächlich derselbe ist. Versionen von 2.4 bis 3.3, während Details über float sich zweimal in der gleichen Zeit geändert haben. Es ist nur so, dass es nicht perfekt ist, weil das unmöglich wäre.) Aber wenn Sie im Voraus wissen, dass Ihre Zahlen alle genau sind In Basis 10 darstellbar, und sie benötigen nicht mehr Ziffern als die von Ihnen konfigurierte Genauigkeit. Dies funktioniert.

64
abarnert

Modulo gibt Ihnen die rest einer Division. 3.5 geteilt durch 0.1 sollte 35 mit einem Rest von 0 ergeben. Aber da Floats auf Potenzen von zwei basieren, sind die Zahlen nicht genau und es treten Rundungsfehler auf.

Wenn Sie eine exakte Division von Dezimalzahlen benötigen, verwenden Sie das Dezimalmodul:

>>> from decimal import Decimal
>>> Decimal('3.5') / Decimal('0.1')
Decimal('35')
>>> Decimal('3.5') % Decimal('0.1')
Decimal('0.0')

Als ich verprügelt werde, dass meine Antwort irreführend ist, kommt die ganze Geschichte:

0.1 ist etwas größer als 0.1

>>> '%.50f' % 0.1
'0.10000000000000000555111512312578270211815834045410'

Wenn Sie den float 3.5 durch eine solche Zahl teilen, erhalten Sie eine Pause von fast 0.1.

Beginnen wir mit der Zahl 0.11 und fügen zwischen den beiden 1 Ziffern weiterhin Nullen ein, um sie kleiner zu machen, während sie größer als 0.1 bleibt.

>>> '%.10f' % (3.5 % 0.101)
'0.0660000000'
>>> '%.10f' % (3.5 % 0.1001)
'0.0966000000'
>>> '%.10f' % (3.5 % 0.10001)
'0.0996600000'
>>> '%.10f' % (3.5 % 0.100001)
'0.0999660000'
>>> '%.10f' % (3.5 % 0.1000001)
'0.0999966000'
>>> '%.10f' % (3.5 % 0.10000001)
'0.0999996600'
>>> '%.10f' % (3.5 % 0.100000001)
'0.0999999660'
>>> '%.10f' % (3.5 % 0.1000000001)
'0.0999999966'
>>> '%.10f' % (3.5 % 0.10000000001)
'0.0999999997'
>>> '%.10f' % (3.5 % 0.100000000001)
'0.1000000000'

Die letzte Zeile gibt den Eindruck, dass wir endlich 0.1 erreicht haben, aber das Ändern der Formatzeichenfolgen zeigt die wahre Natur:

>>> '%.20f' % (3.5 % 0.100000000001)
'0.09999999996600009156'

Das Standard-Float-Format von python zeigt einfach nicht genug Genauigkeit, so dass die 3.5 % 0.1 = 0.1 und 3.5 % 0.1 = 35.0. Es ist wirklich 3.5 % 0.100000... = 0.999999... und 3.5 / 0.100000... = 34.999999..... Im Falle der Teilung erhalten Sie sogar das genaue Ergebnis, da 34.9999... letztendlich ist aufgerundet auf 35.0.


Unterhaltsame Tatsache: Wenn Sie eine Zahl verwenden, die geringfügig kleiner als 0.1 ist, und dieselbe Operation ausführen, erhalten Sie eine Zahl, die geringfügig größer als 0 ist:

>>> 1.0 - 0.9
0.09999999999999998
>>> 35.0 % (1.0 - 0.9)
7.771561172376096e-15
>>> '%.20f' % (35.0 % (1.0 - 0.9))
'0.00000000000000777156'

Mit C++ können Sie sogar zeigen, dass 3.5 geteilt durch den float 0.1 nicht 35 ist, sondern etwas kleiner.

#include <iostream>
#include <iomanip>

int main(int argc, char *argv[]) {
    // double/float, rounding errors do not cancel out
    std::cout << "double/float: " << std::setprecision(20) << 3.5 / 0.1f << std::endl;
    // double/double, rounding errors cancel out
    std::cout << "double/double: " << std::setprecision(20) << 3.5 / 0.1 << std::endl;
    return 0;
}

http://ideone.com/fTNVho

In Python 3.5 / 0.1 erhalten Sie das genaue Ergebnis von 35, da sich die Rundungsfehler gegenseitig aufheben. Es ist wirklich 3.5 / 0.100000... = 34.9999999... Und 34.9999... ist letztendlich so lang, dass Sie genau 35 erhalten. Das C++ - Programm zeigt dies sehr gut, da Sie double und float mischen und mit den Genauigkeiten der Gleitkommazahlen spielen können .

7
bikeshedder

Es hat mit der Ungenauigkeit der Gleitkomma-Arithmetik zu tun. 3.5 % 0.1 holt mich 0.099999999999999811, also Python denkt, dass 0,1 höchstens 34-mal in 3,5 geteilt wird, wobei 0,0999999999999811 übrig bleibt. Ich bin nicht sicher, mit welchem ​​Algorithmus genau dieses Ergebnis erzielt wird, aber das ist der Kern.

1
jjlin