Ein Kommentar im Python-Quellcode für Float-Objekte bestätigt Folgendes:
Der Vergleich ist so ziemlich ein Albtraum
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 / Long w
ist der schlimmste Fall:
v
und w
haben das gleiche Vorzeichen (beide positiv oder beide negativ),
- Die Ganzzahl
w
hat nur wenige Bits, die im size_t
Typ enthalten sein können (normalerweise 32 oder 64 Bit).
- die ganze Zahl
w
hat mindestens 49 Bits,
- Der Exponent des Floats entspricht
v
der Anzahl der Bits in w
.
Und genau das haben wir für die Werte in der Frage:
>>> import math
>>> math.frexp(562949953420000.7) # gives the float's (significand, exponent) pair
(0.9999999999976706, 49)
>>> (562949953421000).bit_length()
49
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 Werten v
und w
.
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
und w
zu zwei entsprechenden C verdoppelt, i
und j
, die dann leicht das richtige Ergebnis zu geben , verglichen werden. Sowohl Python 2 als auch Python 3 verwenden dazu dieselben Ideen (erstere behandeln int
und long
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 zuordnen i
. Als nächstes prüft die Funktion, ob w
es sich auch um ein Float handelt, und ordnet es einem C-Double zu j
. Dies ist das beste Szenario für die Funktion, da alle anderen Überprüfungen übersprungen werden können. Die Funktion kann auch überprüft, ob v
ist inf
oder nan
:
static PyObject*
float_richcompare(PyObject *v, PyObject *w, int op)
{
double i, j;
int r = 0;
assert(PyFloat_Check(v));
i = PyFloat_AS_DOUBLE(v);
if (PyFloat_Check(w))
j = PyFloat_AS_DOUBLE(w);
else if (!Py_IS_FINITE(i)) {
if (PyLong_Check(w))
j = 0.0;
else
goto Unimplemented;
}
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 Vorzeichen v
und das Vorzeichen von zu extrahieren w
(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:
else if (PyLong_Check(w)) {
int vsign = i == 0.0 ? 0 : i < 0.0 ? -1 : 1;
int wsign = _PyLong_Sign(w);
size_t nbits;
int exponent;
if (vsign != wsign) {
/* Magnitudes are irrelevant -- the signs alone
* determine the outcome.
*/
i = (double)vsign;
j = (double)wsign;
goto Compare;
}
}
Wenn diese Prüfung fehlgeschlagen ist , dann v
und w
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 Float v
:
nbits = _PyLong_NumBits(w);
if (nbits == (size_t)-1 && PyErr_Occurred()) {
/* This long is so large that size_t isn't big enough
* to hold the # of bits. Replace with little doubles
* that give the same outcome -- w is so large that
* its magnitude must exceed the magnitude of any
* finite float.
*/
PyErr_Clear();
i = (double)vsign;
assert(wsign != 0);
j = wsign * 2.0;
goto Compare;
}
Wenn die Ganzzahl dagegen w
48 oder weniger Bits hat, kann sie sicher in ein C-Doppel umgewandelt j
und verglichen werden:
if (nbits <= 48) {
j = PyLong_AsDouble(w);
/* It's impossible that <= 48 bits overflowed. */
assert(j != -1.0 || ! PyErr_Occurred());
goto Compare;
}
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:
if (nbits <= 48) {
/* "Multiply both sides" by -1; this also swaps the
* comparator.
*/
i = -i;
op = _Py_SwappedOp[op];
}
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:
(void) frexp(i, &exponent);
if (exponent < 0 || (size_t)exponent < nbits) {
i = 1.0;
j = 2.0;
goto Compare;
}
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 , dass v < |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 so v > |w|
:
if ((size_t)exponent > nbits) {
i = 2.0;
j = 1.0;
goto Compare;
}
Wenn diese Prüfung nicht erfolgreich war, wissen wir, dass der Exponent des Gleitkommas v
der Anzahl der Bits in der Ganzzahl entspricht w
.
Die einzige Möglichkeit, die beiden Werte jetzt zu vergleichen, besteht darin, zwei neue Python-Ganzzahlen aus v
und zu erstellen w
. Die Idee ist, den Bruchteil von zu verwerfen v
, 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 Werten 4.65 < 4
würde durch den Vergleich bestimmt (2*4)+1 == 9 < 8 == (2*4)
(Rückgabe von false).
{
double fracpart;
double intpart;
PyObject *result = NULL;
PyObject *one = NULL;
PyObject *vv = NULL;
PyObject *ww = w;
// snip
fracpart = modf(i, &intpart); // split i (the double that v mapped to)
vv = PyLong_FromDouble(intpart);
// snip
if (fracpart != 0.0) {
/* Shift left, and or a 1 bit into vv
* to represent the lost fraction.
*/
PyObject *temp;
one = PyLong_FromLong(1);
temp = PyNumber_Lshift(ww, one); // left-shift doubles an integer
ww = temp;
temp = PyNumber_Lshift(vv, one);
vv = temp;
temp = PyNumber_Or(vv, one); // a doubled integer is even, so this adds 1
vv = temp;
}
// snip
}
}
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, wenn w
ist auch ein Schwimmer:
Überprüfen Sie, ob w
ist nan
oder inf
. Wenn ja, behandeln Sie diesen Sonderfall je nach Art separat w
.
Wenn nicht, vergleichen Sie v
und w
direkt durch ihre Darstellungen als C verdoppelt sich.
Wenn w
ist eine ganze Zahl:
Extrahieren Sie die Zeichen von v
und w
. Wenn sie unterschiedlich sind, dann wissen wir v
und w
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 als size_t
). Wenn ja, w
hat eine größere Größe als v
.
Ü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 werden v
.
( w
hat mehr als 48 Bits. Wir werden jetzt w
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 als 1
und daher kleiner als jede positive ganze Zahl. Andernfalls muss der Exponent kleiner als die Anzahl der Bits w
sein w
.
Wenn der Exponent von v
größer als die Anzahl der Bits w
ist, v
ist er größer als w
.
( 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 Zahl w
. Vergleichen Sie stattdessen diese beiden neuen Ganzzahlen, um das Ergebnis zu erhalten.