Un commentaire dans le code source Python pour les objets flottants reconnaît que:
La comparaison est à peu près un cauchemar
Cela est particulièrement vrai lors de la comparaison d'un flottant à un entier, car, contrairement aux flottants, les entiers en Python peuvent être arbitrairement grands et sont toujours exacts. Essayer de convertir l'entier en un flottant peut perdre en précision et rendre la comparaison inexacte. Essayer de convertir le flotteur en un entier ne fonctionnera pas non plus car toute partie fractionnaire sera perdue.
Pour contourner ce problème, Python effectue une série de vérifications, renvoyant le résultat si l'une des vérifications réussit. Il compare les signes des deux valeurs, puis si l'entier est "trop grand" pour être un flottant, puis compare l'exposant du flotteur à la longueur de l'entier. Si toutes ces vérifications échouent, il est nécessaire de construire deux nouveaux objets Python à comparer afin d'obtenir le résultat.
Lorsque l'on compare un flottant v
à un entier / long w
, le pire des cas est que:
v
et w
avoir le même signe (positif ou négatif),
- l'entier
w
a assez peu de bits pour pouvoir être contenu dans le size_t
type (typiquement 32 ou 64 bits),
- l'entier
w
a au moins 49 bits,
- l'exposant du flottant
v
est le même que le nombre de bits w
.
Et c'est exactement ce que nous avons pour les valeurs de la question:
>>> import math
>>> math.frexp(562949953420000.7) # gives the float's (significand, exponent) pair
(0.9999999999976706, 49)
>>> (562949953421000).bit_length()
49
Nous voyons que 49 est à la fois l'exposant du flottant et le nombre de bits dans l'entier. Les deux chiffres sont positifs et les quatre critères ci-dessus sont donc remplis.
Choisir une des valeurs pour être plus grande (ou plus petite) peut changer le nombre de bits de l'entier, ou la valeur de l'exposant, et ainsi Python est capable de déterminer le résultat de la comparaison sans effectuer la vérification finale coûteuse.
Ceci est spécifique à l'implémentation CPython du langage.
La comparaison plus en détail
La float_richcompare
fonction gère la comparaison entre deux valeurs v
et w
.
Vous trouverez ci-dessous une description pas à pas des contrôles effectués par la fonction. Les commentaires dans la source Python sont en fait très utiles lorsque vous essayez de comprendre ce que fait la fonction, je les ai donc laissés là où ils sont pertinents. J'ai également résumé ces vérifications dans une liste au bas de la réponse.
L'idée principale est de mapper les objets Python v
et w
deux C doubles appropriés, i
et j
, qui peuvent ensuite être facilement comparés pour donner le résultat correct. Python 2 et Python 3 utilisent les mêmes idées pour ce faire (l'ancien ne gère int
et ne long
tape que séparément).
La première chose à faire est de vérifier que v
est sans aucun doute un flotteur Python et la carte à un C à double i
. Suivant la fonction examine si w
est aussi un flotteur et cartes à C doubles j
. Il s'agit du meilleur scénario pour la fonction, car toutes les autres vérifications peuvent être ignorées. La fonction vérifie également si v
est inf
ou 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;
}
Nous savons maintenant que si w
ces vérifications ont échoué, ce n'est pas un flotteur Python. Maintenant, la fonction vérifie s'il s'agit d'un entier Python. Si tel est le cas, le test le plus simple est d'extraire le signe de v
et le signe de w
(retourner 0
si zéro, -1
si négatif, 1
si positif). Si les signes sont différents, ce sont toutes les informations nécessaires pour retourner le résultat de la comparaison:
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;
}
}
Si cette vérification a échoué, alors v
et w
portez le même signe.
La vérification suivante compte le nombre de bits dans l'entier w
. S'il contient trop de bits, il ne peut pas être considéré comme un flottant et doit donc être plus grand que le flotteur 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'un autre côté, si l'entier w
a 48 bits ou moins, il peut en toute sécurité être transformé en double C j
et comparé:
if (nbits <= 48) {
j = PyLong_AsDouble(w);
/* It's impossible that <= 48 bits overflowed. */
assert(j != -1.0 || ! PyErr_Occurred());
goto Compare;
}
À partir de ce moment, nous savons qu'il w
a 49 bits ou plus. Il sera commode de traiter w
comme un entier positif, changez donc le signe et l'opérateur de comparaison si nécessaire:
if (nbits <= 48) {
/* "Multiply both sides" by -1; this also swaps the
* comparator.
*/
i = -i;
op = _Py_SwappedOp[op];
}
Maintenant, la fonction regarde l'exposant du flotteur. Rappelons qu'un flottant peut s'écrire (signe ignorant) comme exposant de significande * 2 et que la significande représente un nombre compris entre 0,5 et 1:
(void) frexp(i, &exponent);
if (exponent < 0 || (size_t)exponent < nbits) {
i = 1.0;
j = 2.0;
goto Compare;
}
Cela vérifie deux choses. Si l'exposant est inférieur à 0, le flottant est inférieur à 1 (et donc plus petit que n'importe quel entier). Ou, si l'exposant est inférieur au nombre de bits, w
alors nous avons cela v < |w|
puisque l' exposant significand * 2 est inférieur à 2 nbits .
A défaut de ces deux vérifications, la fonction cherche à voir si l'exposant est supérieur au nombre de bits w
. Cela montre que l' exposant significand * 2 est supérieur à 2 nbits et donc v > |w|
:
if ((size_t)exponent > nbits) {
i = 2.0;
j = 1.0;
goto Compare;
}
Si cette vérification n'a pas réussi, nous savons que l'exposant du flottant v
est le même que le nombre de bits dans l'entier w
.
La seule façon de comparer les deux valeurs maintenant est de construire deux nouveaux entiers Python à partir de v
et w
. L'idée est de supprimer la partie fractionnaire de v
, de doubler la partie entière, puis d'en ajouter une. w
est également doublé et ces deux nouveaux objets Python peuvent être comparés pour donner la valeur de retour correcte. L'utilisation d'un exemple avec de petites valeurs 4.65 < 4
serait déterminée par la comparaison (2*4)+1 == 9 < 8 == (2*4)
(retour faux).
{
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
}
}
Par souci de concision, j'ai omis la vérification d'erreur supplémentaire et le suivi des ordures que Python doit faire lorsqu'il crée ces nouveaux objets. Il va sans dire que cela ajoute des frais généraux supplémentaires et explique pourquoi les valeurs mises en évidence dans la question sont beaucoup plus lentes à comparer que les autres.
Voici un résumé des contrôles effectués par la fonction de comparaison.
Soit v
un flotteur et lancez-le comme un double C. Maintenant, si w
est également un flotteur:
Vérifiez si w
est nan
ou inf
. Si c'est le cas, manipulez ce cas spécial séparément en fonction du type de w
.
Sinon, comparez v
et w
directement par leurs représentations comme C doubles.
Si w
est un entier:
Extraire les signes de v
et w
. S'ils sont différents, alors nous savons v
et w
sommes différents et quelle est la plus grande valeur.
( Les signes sont les mêmes. ) Vérifiez s'il y w
a trop de bits pour être un flottant (plus que size_t
). Si oui, w
a une magnitude supérieure à v
.
Vérifiez s'il w
a 48 bits ou moins. Si c'est le cas, il peut être coulé en toute sécurité dans un double C sans perdre sa précision et par rapport à v
.
( w
a plus de 48 bits. Nous allons maintenant traiter w
comme un entier positif ayant changé l'op de comparaison comme il convient. )
Considérez l'exposant du flotteur v
. Si l'exposant est négatif, il v
est alors inférieur 1
et donc inférieur à tout entier positif. Sinon, si l'exposant est inférieur au nombre de bits, w
il doit être inférieur à w
.
Si l'exposant de v
est supérieur au nombre de bits, w
alors v
est supérieur à w
.
( L'exposant est le même que le nombre de bits w
. )
Le contrôle final. Divisé v
en ses parties entières et fractionnaires. Doublez la partie entière et ajoutez 1 pour compenser la partie fractionnaire. Maintenant, doublez l'entier w
. Comparez plutôt ces deux nouveaux entiers pour obtenir le résultat.