Un commento nel codice sorgente Python per oggetti float riconosce che:
Il confronto è praticamente un incubo
Ciò è particolarmente vero quando si confronta un float con un numero intero, perché, a differenza dei float, i numeri interi in Python possono essere arbitrariamente grandi e sono sempre esatti. Cercare di lanciare il numero intero su un float potrebbe perdere precisione e rendere impreciso il confronto. Cercare di lanciare il float su un numero intero non funzionerà nemmeno perché qualsiasi parte frazionaria andrà persa.
Per aggirare questo problema, Python esegue una serie di controlli, restituendo il risultato se uno dei controlli ha esito positivo. Confronta i segni dei due valori, quindi se l'intero è "troppo grande" per essere un float, quindi confronta l'esponente del float con la lunghezza dell'intero. Se tutti questi controlli falliscono, è necessario costruire due nuovi oggetti Python da confrontare per ottenere il risultato.
Quando si confronta un float vcon un numero intero / long w, il caso peggiore è che:
ve whanno lo stesso segno (sia positivo che entrambi negativi),
- il numero intero
wha pochi bit sufficienti da poter essere mantenuto nel size_ttipo (in genere 32 o 64 bit),
- l'intero
wha almeno 49 bit,
- l'esponente del float
vè uguale al numero di bit in w.
E questo è esattamente ciò che abbiamo per i valori nella domanda:
>>> import math
>>> math.frexp(562949953420000.7) # gives the float's (significand, exponent) pair
(0.9999999999976706, 49)
>>> (562949953421000).bit_length()
49
Vediamo che 49 è sia l'esponente del float che il numero di bit nell'intero. Entrambi i numeri sono positivi e quindi i quattro criteri sopra sono soddisfatti.
La scelta di uno dei valori più grandi (o più piccoli) può modificare il numero di bit dell'intero o il valore dell'esponente, quindi Python è in grado di determinare il risultato del confronto senza eseguire il costoso controllo finale.
Questo è specifico per l'implementazione CPython del linguaggio.
Il confronto in modo più dettagliato
La float_richcomparefunzione gestisce il confronto tra due valori ve w.
Di seguito è riportata una descrizione dettagliata dei controlli eseguiti dalla funzione. I commenti nella fonte Python sono in realtà molto utili quando si cerca di capire cosa fa la funzione, quindi li ho lasciati pertinenti. Ho anche riassunto questi controlli in un elenco ai piedi della risposta.
L'idea principale è quella di mappare gli oggetti Python ve wdue doppi C appropriati ie j, che possono quindi essere facilmente confrontati per dare il risultato corretto. Sia Python 2 che Python 3 usano le stesse idee per farlo (il primo gestisce inte longdigita separatamente).
La prima cosa da fare è controllare che vè sicuramente un galleggiante Python e la mappa a una doppia C i. Avanti gli sguardi funzione in se wè anche un galleggiante e mappe ad una doppia C j. Questo è lo scenario migliore per la funzione poiché è possibile saltare tutti gli altri controlli. La funzione controlla anche se vè info 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;
}
Ora sappiamo che se wquesti controlli falliscono, non è un float Python. Ora la funzione controlla se è un numero intero Python. In questo caso, il test più semplice è quello di estrarre il segno di ve il segno di w(restituire 0se zero, -1se negativo, 1se positivo). Se i segni sono diversi, queste sono tutte le informazioni necessarie per restituire il risultato del confronto:
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;
}
}
Se questo controllo ha esito negativo, quindi ve whanno lo stesso segno.
Il controllo successivo conta il numero di bit nell'intero w. Se ha troppi bit, non può essere tenuto come un galleggiante e quindi deve avere una grandezza maggiore rispetto al galleggiante 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;
}
D'altra parte, se l'intero wha 48 o meno bit, può tranquillamente essere convertito in una doppia C je confrontato:
if (nbits <= 48) {
j = PyLong_AsDouble(w);
/* It's impossible that <= 48 bits overflowed. */
assert(j != -1.0 || ! PyErr_Occurred());
goto Compare;
}
Da questo punto in poi, sappiamo che wha 49 o più bit. Sarà conveniente trattare wcome un numero intero positivo, quindi cambia il segno e l'operatore di confronto come necessario:
if (nbits <= 48) {
/* "Multiply both sides" by -1; this also swaps the
* comparator.
*/
i = -i;
op = _Py_SwappedOp[op];
}
Ora la funzione guarda l'esponente del galleggiante. Ricorda che un float può essere scritto (ignorando il segno) come significante * 2 esponente e che il significante rappresenta un numero compreso tra 0,5 e 1:
(void) frexp(i, &exponent);
if (exponent < 0 || (size_t)exponent < nbits) {
i = 1.0;
j = 2.0;
goto Compare;
}
Questo controlla due cose. Se l'esponente è inferiore a 0, il galleggiante è minore di 1 (e quindi di dimensioni inferiori rispetto a qualsiasi numero intero). Oppure, se l'esponente è inferiore al numero di bit, wallora abbiamo che v < |w|poiché esponente significante * 2 è inferiore a 2 nbit .
In mancanza di questi due controlli, la funzione cerca di vedere se l'esponente è maggiore del numero di bit in w. Questo dimostra che significante * 2 esponente è maggiore di 2 nbit e quindi v > |w|:
if ((size_t)exponent > nbits) {
i = 2.0;
j = 1.0;
goto Compare;
}
Se questo controllo non ha esito positivo, sappiamo che l'esponente del float vè uguale al numero di bit nell'intero w.
L'unico modo per confrontare i due valori ora è costruire due nuovi numeri interi Python da ve w. L'idea è di scartare la parte frazionaria di v, raddoppiare la parte intera e quindi aggiungerne una. wviene anche raddoppiato e questi due nuovi oggetti Python possono essere confrontati per fornire il valore di ritorno corretto. Utilizzando un esempio con valori piccoli, 4.65 < 4sarebbe determinato dal confronto (2*4)+1 == 9 < 8 == (2*4)(restituendo 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
}
}
Per brevità ho tralasciato il controllo degli errori aggiuntivo e il tracciamento dei rifiuti che Python deve fare quando crea questi nuovi oggetti. Inutile dire che questo aggiunge un sovraccarico aggiuntivo e spiega perché i valori evidenziati nella domanda sono significativamente più lenti da confrontare rispetto ad altri.
Ecco un riepilogo dei controlli eseguiti dalla funzione di confronto.
Lascia che vsia un float e lancialo come double C. Ora, se wè anche un float:
Controlla se lo wè nano inf. In tal caso, maneggiare questo caso speciale separatamente a seconda del tipo di w.
In caso contrario, confrontare ve wdirettamente con le loro rappresentazioni come doppi di C.
Se wè un numero intero:
Estrarre i segni di ve w. Se sono diversi, allora lo sappiamo ve wsono diversi e qual è il valore maggiore.
( I segni sono gli stessi. ) Verifica se wha troppi bit per essere un float (più di size_t). In tal caso, wha una grandezza maggiore di v.
Controlla se wha 48 o meno bit. In tal caso, può essere tranquillamente lanciato su un doppio C senza perdere la sua precisione e confrontato con v.
( wha più di 48 bit. Tratteremo ora wun numero intero positivo dopo aver modificato l'operazione di confronto nel modo appropriato. )
Considera l'esponente del galleggiante v. Se l'esponente è negativo, allora vè inferiore 1e quindi inferiore a qualsiasi numero intero positivo. Altrimenti, se l'esponente è inferiore al numero di bit in wallora deve essere inferiore a w.
Se l'esponente di vè maggiore del numero di bit in wallora vè maggiore di w.
( L'esponente è uguale al numero di bit in w. )
Il controllo finale. Dividi vnelle sue parti intere e frazionarie. Raddoppia la parte intera e aggiungi 1 per compensare la parte frazionaria. Ora raddoppia il numero intero w. Confronta invece questi due nuovi numeri interi per ottenere il risultato.