Beim Vergleich von Floats mit ganzen Zahlen dauert die Auswertung einiger Wertepaare viel länger als bei anderen Werten ähnlicher Größe.
Beispielsweise:
>>> import timeit
>>> timeit.timeit("562949953420000.7 < 562949953421000") # run 1 million times
0.5387085462592742
Wenn der Gleitkomma oder die Ganzzahl jedoch um einen bestimmten Betrag kleiner oder größer wird, läuft der Vergleich viel schneller ab:
>>> timeit.timeit("562949953420000.7 < 562949953422000") # integer increased by 1000
0.1481498428446173
>>> timeit.timeit("562949953423001.8 < 562949953421000") # float increased by 3001.1
0.1459577925548956
Das Ändern des Vergleichsoperators (z. B. Verwenden von ==
oder >
stattdessen) wirkt sich nicht merklich auf die Zeiten aus.
Dies hängt nicht nur mit der Größe zusammen, da die Auswahl größerer oder kleinerer Werte zu schnelleren Vergleichen führen kann. Ich vermute, dass dies auf eine unglückliche Art und Weise zurückzuführen ist, in der die Bits ausgerichtet sind.
Der Vergleich dieser Werte ist für die meisten Anwendungsfälle eindeutig mehr als schnell genug. Ich bin einfach neugierig, warum Python mit einigen Wertepaaren mehr zu kämpfen hat als mit anderen.
quelle
Antworten:
Ein Kommentar im Python-Quellcode für Float-Objekte bestätigt Folgendes:
Dies gilt insbesondere beim Vergleich eines Floats mit einer Ganzzahl, da Ganzzahlen in Python im Gegensatz zu Floats beliebig groß sein können und immer genau sind. Der Versuch, die Ganzzahl in einen Float umzuwandeln, kann an Genauigkeit verlieren und den Vergleich ungenau machen. Der Versuch, den Float in eine Ganzzahl umzuwandeln, funktioniert ebenfalls nicht, da ein Bruchteil verloren geht.
Um dieses Problem zu umgehen, führt Python eine Reihe von Überprüfungen durch und gibt das Ergebnis zurück, wenn eine der Überprüfungen erfolgreich ist. Es vergleicht die Vorzeichen der beiden Werte, ob die Ganzzahl "zu groß" ist, um ein Float zu sein, und vergleicht dann den Exponenten des Floats mit der Länge der Ganzzahl. Wenn alle diese Überprüfungen fehlschlagen, müssen zwei neue Python-Objekte zum Vergleich erstellt werden, um das Ergebnis zu erhalten.
Beim Vergleich eines Floats
v
mit einer Ganzzahl / Longw
ist der schlimmste Fall:v
undw
haben das gleiche Vorzeichen (beide positiv oder beide negativ),w
hat nur wenige Bits, die imsize_t
Typ enthalten sein können (normalerweise 32 oder 64 Bit).w
hat mindestens 49 Bits,v
der Anzahl der Bits inw
.Und genau das haben wir für die Werte in der Frage:
Wir sehen, dass 49 sowohl der Exponent des Floats als auch die Anzahl der Bits in der Ganzzahl ist. Beide Zahlen sind positiv und somit sind die vier oben genannten Kriterien erfüllt.
Wenn Sie einen der Werte als größer (oder kleiner) auswählen, kann sich die Anzahl der Bits der Ganzzahl oder der Wert des Exponenten ändern, sodass Python das Ergebnis des Vergleichs ermitteln kann, ohne die teure Endprüfung durchzuführen.
Dies ist spezifisch für die CPython-Implementierung der Sprache.
Der Vergleich im Detail
Die
float_richcompare
Funktion übernimmt den Vergleich zwischen zwei Wertenv
undw
.Im Folgenden finden Sie eine schrittweise Beschreibung der von der Funktion durchgeführten Überprüfungen. Die Kommentare in der Python-Quelle sind tatsächlich sehr hilfreich, wenn Sie versuchen zu verstehen, was die Funktion tut. Deshalb habe ich sie dort belassen, wo sie relevant sind. Ich habe diese Überprüfungen auch in einer Liste am Fuße der Antwort zusammengefasst.
Die Hauptidee ist die Python - Objekte abzubilden
v
undw
zu zwei entsprechenden C verdoppelt,i
undj
, die dann leicht das richtige Ergebnis zu geben , verglichen werden. Sowohl Python 2 als auch Python 3 verwenden dazu dieselben Ideen (erstere behandelnint
undlong
tippen nur separat).Als erstes müssen Sie überprüfen, ob
v
es sich definitiv um einen Python-Float handelt, und ihn einem C-Double zuordneni
. Als nächstes prüft die Funktion, obw
es sich auch um ein Float handelt, und ordnet es einem C-Double zuj
. Dies ist das beste Szenario für die Funktion, da alle anderen Überprüfungen übersprungen werden können. Die Funktion kann auch überprüft, obv
istinf
odernan
:Jetzt wissen wir, dass es sich bei
w
diesen Überprüfungen nicht um einen Python-Float handelt. Jetzt prüft die Funktion, ob es sich um eine Python-Ganzzahl handelt. Wenn dies der Fall ist, besteht der einfachste Test darin, das Vorzeichenv
und das Vorzeichen von zu extrahierenw
(Rückgabe,0
wenn Null,-1
wenn negativ,1
wenn positiv). Wenn die Vorzeichen unterschiedlich sind, sind dies alle Informationen, die erforderlich sind, um das Ergebnis des Vergleichs zurückzugeben:Wenn diese Prüfung fehlgeschlagen ist , dann
v
undw
das gleiche Vorzeichen haben .Die nächste Prüfung zählt die Anzahl der Bits in der Ganzzahl
w
. Wenn es zu viele Bits hat, kann es möglicherweise nicht als Float gehalten werden und muss daher größer sein als der Floatv
:Wenn die Ganzzahl dagegen
w
48 oder weniger Bits hat, kann sie sicher in ein C-Doppel umgewandeltj
und verglichen werden:Ab diesem Zeitpunkt wissen wir, dass
w
49 oder mehr Bits vorhanden sind. Es ist praktisch,w
eine positive Ganzzahl zu behandeln. Ändern Sie daher das Vorzeichen und den Vergleichsoperator nach Bedarf:Jetzt betrachtet die Funktion den Exponenten des Floats. Denken Sie daran, dass ein Float als Exponent des Signifikanten * 2 geschrieben werden kann (Vorzeichen ignorieren) und dass der Signifikand eine Zahl zwischen 0,5 und 1 darstellt:
Dies überprüft zwei Dinge. Wenn der Exponent kleiner als 0 ist, ist der Float kleiner als 1 (und daher kleiner als jede ganze Zahl). Oder, wenn der Exponent kleiner ist als die Anzahl der Bits in
w
dann haben wir , dassv < |w|
seit Signifikanden * 2 Exponenten weniger als 2 nBits .Wenn diese beiden Prüfungen fehlschlagen, prüft die Funktion, ob der Exponent größer als die Anzahl der Bit-In ist
w
. Dies zeigt, dass der Exponent des Signifikanten * 2 größer als 2 nbit ist und sov > |w|
:Wenn diese Prüfung nicht erfolgreich war, wissen wir, dass der Exponent des Gleitkommas
v
der Anzahl der Bits in der Ganzzahl entsprichtw
.Die einzige Möglichkeit, die beiden Werte jetzt zu vergleichen, besteht darin, zwei neue Python-Ganzzahlen aus
v
und zu erstellenw
. Die Idee ist, den Bruchteil von zu verwerfenv
, den ganzzahligen Teil zu verdoppeln und dann einen hinzuzufügen.w
wird ebenfalls verdoppelt und diese beiden neuen Python-Objekte können verglichen werden, um den korrekten Rückgabewert zu erhalten. Die Verwendung eines Beispiels mit kleinen Werten4.65 < 4
würde durch den Vergleich bestimmt(2*4)+1 == 9 < 8 == (2*4)
(Rückgabe von false).Der Kürze halber habe ich die zusätzliche Fehlerprüfung und Müllverfolgung, die Python beim Erstellen dieser neuen Objekte durchführen muss, weggelassen. Dies erhöht natürlich den zusätzlichen Aufwand und erklärt, warum die in der Frage hervorgehobenen Werte wesentlich langsamer zu vergleichen sind als andere.
Hier ist eine Zusammenfassung der Überprüfungen, die von der Vergleichsfunktion durchgeführt werden.
Sei
v
ein Schwimmer und wirf ihn als C-Doppel. Nun, wennw
ist auch ein Schwimmer:Überprüfen Sie, ob
w
istnan
oderinf
. Wenn ja, behandeln Sie diesen Sonderfall je nach Art separatw
.Wenn nicht, vergleichen Sie
v
undw
direkt durch ihre Darstellungen als C verdoppelt sich.Wenn
w
ist eine ganze Zahl:Extrahieren Sie die Zeichen von
v
undw
. Wenn sie unterschiedlich sind, dann wissen wirv
undw
sind unterschiedlich und welches ist der größere Wert.( Die Vorzeichen sind die gleichen. ) Überprüfen Sie, ob
w
zu viele Bits vorhanden sind, um ein Float zu sein (mehr alssize_t
). Wenn ja,w
hat eine größere Größe alsv
.Überprüfen Sie, ob
w
48 oder weniger Bits vorhanden sind. Wenn ja, kann es sicher in ein C-Doppel gegossen werden, ohne seine Präzision zu verlieren, und mit verglichen werdenv
.(
w
hat mehr als 48 Bits. Wir werden jetztw
als positive Ganzzahl behandeln, nachdem wir die Vergleichsoperation entsprechend geändert haben. )Betrachten Sie den Exponenten des Schwimmers
v
. Wenn der Exponent negativ ist,v
ist er kleiner als1
und daher kleiner als jede positive ganze Zahl. Andernfalls muss der Exponent kleiner als die Anzahl der Bitsw
seinw
.Wenn der Exponent von
v
größer als die Anzahl der Bitsw
ist,v
ist er größer alsw
.( Der Exponent entspricht der Anzahl der Bits in
w
. )Die letzte Überprüfung. Split
v
in die ganzzahlige und gebrochene Teile. Verdoppeln Sie den ganzzahligen Teil und addieren Sie 1, um den Bruchteil zu kompensieren. Verdoppeln Sie jetzt die ganze Zahlw
. Vergleichen Sie stattdessen diese beiden neuen Ganzzahlen, um das Ergebnis zu erhalten.quelle
Mit
gmpy2
Floats und Ganzzahlen mit beliebiger Genauigkeit ist es möglich, eine einheitlichere Vergleichsleistung zu erzielen:quelle
decimal
in der Standardbibliothek