Lorsque vous comparez des flottants à des nombres entiers, certaines paires de valeurs prennent beaucoup plus de temps à être évaluées que d'autres valeurs de même ampleur.
Par exemple:
>>> import timeit
>>> timeit.timeit("562949953420000.7 < 562949953421000") # run 1 million times
0.5387085462592742
Mais si le flottant ou l'entier est réduit ou agrandi d'une certaine quantité, la comparaison s'exécute beaucoup plus rapidement:
>>> timeit.timeit("562949953420000.7 < 562949953422000") # integer increased by 1000
0.1481498428446173
>>> timeit.timeit("562949953423001.8 < 562949953421000") # float increased by 3001.1
0.1459577925548956
Changer l'opérateur de comparaison (par exemple en utilisant ==
ou à la >
place) n'affecte pas les temps de manière notable.
Ce n'est pas uniquement lié à la magnitude, car choisir des valeurs plus grandes ou plus petites peut entraîner des comparaisons plus rapides, donc je soupçonne que cela est dû à une façon malheureuse que les bits s'alignent.
De toute évidence, la comparaison de ces valeurs est plus que suffisamment rapide pour la plupart des cas d'utilisation. Je suis simplement curieux de savoir pourquoi Python semble lutter plus avec certaines paires de valeurs qu'avec d'autres.
la source
Réponses:
Un commentaire dans le code source Python pour les objets flottants reconnaît que:
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 / longw
, le pire des cas est que:v
etw
avoir le même signe (positif ou négatif),w
a assez peu de bits pour pouvoir être contenu dans lesize_t
type (typiquement 32 ou 64 bits),w
a au moins 49 bits,v
est le même que le nombre de bitsw
.Et c'est exactement ce que nous avons pour les valeurs de la question:
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 valeursv
etw
.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
etw
deux C doubles appropriés,i
etj
, 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èreint
et nelong
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 à doublei
. Suivant la fonction examine siw
est aussi un flotteur et cartes à C doublesj
. 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 siv
estinf
ounan
: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 dev
et le signe dew
(retourner0
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:Si cette vérification a échoué, alors
v
etw
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 flotteurv
:D'un autre côté, si l'entier
w
a 48 bits ou moins, il peut en toute sécurité être transformé en double Cj
et comparé:À partir de ce moment, nous savons qu'il
w
a 49 bits ou plus. Il sera commode de traiterw
comme un entier positif, changez donc le signe et l'opérateur de comparaison si nécessaire: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:
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 celav < |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 doncv > |w|
: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'entierw
.La seule façon de comparer les deux valeurs maintenant est de construire deux nouveaux entiers Python à partir de
v
etw
. L'idée est de supprimer la partie fractionnaire dev
, 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 valeurs4.65 < 4
serait déterminée par la comparaison(2*4)+1 == 9 < 8 == (2*4)
(retour faux).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, siw
est également un flotteur:Vérifiez si
w
estnan
ouinf
. Si c'est le cas, manipulez ce cas spécial séparément en fonction du type dew
.Sinon, comparez
v
etw
directement par leurs représentations comme C doubles.Si
w
est un entier:Extraire les signes de
v
etw
. S'ils sont différents, alors nous savonsv
etw
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 quesize_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 traiterw
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, ilv
est alors inférieur1
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
alorsv
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'entierw
. Comparez plutôt ces deux nouveaux entiers pour obtenir le résultat.la source
Utilisation
gmpy2
de flotteurs de précision arbitraires et des entiers , il est possible d'obtenir une comparaison plus uniforme performance:la source
decimal
dans la bibliothèque standard