Al comparar flotantes con enteros, algunos pares de valores tardan mucho más en evaluarse que otros valores de una magnitud similar.
Por ejemplo:
>>> import timeit
>>> timeit.timeit("562949953420000.7 < 562949953421000") # run 1 million times
0.5387085462592742
Pero si el flotante o entero se hace más pequeño o más grande en cierta cantidad, la comparación se ejecuta mucho más rápidamente:
>>> timeit.timeit("562949953420000.7 < 562949953422000") # integer increased by 1000
0.1481498428446173
>>> timeit.timeit("562949953423001.8 < 562949953421000") # float increased by 3001.1
0.1459577925548956
Cambiar el operador de comparación (por ejemplo, usar ==
o en su >
lugar) no afecta los tiempos de manera notable.
Esto no se relaciona únicamente con la magnitud porque elegir valores más grandes o más pequeños puede resultar en comparaciones más rápidas, por lo que sospecho que se debe a una desafortunada forma en que los bits se alinean.
Claramente, comparar estos valores es más que suficientemente rápido para la mayoría de los casos de uso. Simplemente tengo curiosidad por saber por qué Python parece luchar más con algunos pares de valores que con otros.
fuente
Respuestas:
Un comentario en el código fuente de Python para objetos flotantes reconoce que:
Esto es especialmente cierto cuando se compara un flotante con un entero, porque, a diferencia de los flotantes, los enteros en Python pueden ser arbitrariamente grandes y siempre son exactos. Intentar convertir el entero en un flotador puede perder precisión y hacer que la comparación sea inexacta. Intentar lanzar el flotador a un número entero tampoco funcionará porque se perderá cualquier parte fraccional.
Para solucionar este problema, Python realiza una serie de comprobaciones, devolviendo el resultado si una de las comprobaciones tiene éxito. Compara los signos de los dos valores, luego si el número entero es "demasiado grande" para ser un flotante, luego compara el exponente del flotante con la longitud del entero. Si todas estas comprobaciones fallan, es necesario construir dos nuevos objetos de Python para comparar para obtener el resultado.
Al comparar un flotante
v
con un entero / largow
, el peor de los casos es que:v
yw
tienen el mismo signo (tanto positivo como negativo),w
tiene pocos bits suficientes para que pueda mantenerse en elsize_t
tipo (generalmente 32 o 64 bits),w
tiene al menos 49 bits,v
es igual al número de bits enw
.Y esto es exactamente lo que tenemos para los valores en la pregunta:
Vemos que 49 es tanto el exponente del flotante como el número de bits en el entero. Ambos números son positivos, por lo que se cumplen los cuatro criterios anteriores.
Elegir uno de los valores para que sea más grande (o más pequeño) puede cambiar el número de bits del número entero o el valor del exponente, por lo que Python puede determinar el resultado de la comparación sin realizar la costosa verificación final.
Esto es específico para la implementación de CPython del lenguaje.
La comparación con más detalle.
La
float_richcompare
función maneja la comparación entre dos valoresv
yw
.A continuación se muestra una descripción paso a paso de las comprobaciones que realiza la función. Los comentarios en la fuente de Python son realmente muy útiles cuando se trata de entender lo que hace la función, por lo que los dejé en su lugar. También resumí estos controles en una lista al pie de la respuesta.
La idea principal es mapear los objetos de Python
v
yw
dos dobles C apropiados,i
yj
que luego se pueden comparar fácilmente para obtener el resultado correcto. Tanto Python 2 como Python 3 usan las mismas ideas para hacer esto (el primero solo manejaint
ylong
escribe por separado).Lo primero que debe hacer es comprobar que
v
es sin duda un flotador Python y asignarla a una doble Ci
. A continuación, las miradas de función en siw
es también un flotador y lo asigna a una doble Cj
. Este es el mejor de los casos para la función, ya que se pueden omitir todas las demás comprobaciones. La función también verifica siv
esinf
onan
:Ahora sabemos que si
w
fallaron estas comprobaciones, no es un flotador de Python. Ahora la función comprueba si es un entero de Python. Si este es el caso, la prueba más fácil es extraer el signov
y el signo dew
(devolver0
si es cero,-1
si es negativo,1
si es positivo). Si los signos son diferentes, esta es toda la información necesaria para devolver el resultado de la comparación:Si esta verificación falló, entonces
v
yw
tenga el mismo signo.La siguiente verificación cuenta el número de bits en el entero
w
. Si tiene demasiados bits, no es posible mantenerlo como flotante y, por lo tanto, debe ser de mayor magnitud que el flotantev
:Por otro lado, si el entero
w
tiene 48 bits o menos, puede convertir C doble de forma seguraj
y comparar:A partir de este momento, sabemos que
w
tiene 49 o más bits. Será conveniente tratarlow
como un entero positivo, así que cambie el signo y el operador de comparación según sea necesario:Ahora la función mira el exponente del flotador. Recuerde que se puede escribir un flotador (ignorando el signo) como exponente significand * 2 y que el significand representa un número entre 0.5 y 1:
Esto verifica dos cosas. Si el exponente es menor que 0, el flotador es menor que 1 (y por lo tanto, de menor magnitud que cualquier número entero). O, si el exponente es menor que el número de bits,
w
entonces tenemos eso,v < |w|
ya que el exponente significativo * 2 es menor que 2 nbits .Al fallar estas dos comprobaciones, la función busca ver si el exponente es mayor que el número de bits
w
. Esto muestra que significante * 2 exponente es mayor que 2 nbits y asív > |w|
:Si esta comprobación no tuvo éxito, sabemos que el exponente del flotante
v
es el mismo que el número de bits en el enterow
.La única forma de comparar los dos valores ahora es construir dos nuevos enteros de Python a partir de
v
yw
. La idea es descartar la parte fraccional dev
, duplicar la parte entera y luego agregar una.w
también se duplica y estos dos nuevos objetos de Python se pueden comparar para obtener el valor de retorno correcto. El uso de un ejemplo con valores pequeños4.65 < 4
se determinaría mediante la comparación(2*4)+1 == 9 < 8 == (2*4)
(que devuelve falso).Por brevedad, he omitido la comprobación adicional de errores y el seguimiento de basura que Python tiene que hacer cuando crea estos nuevos objetos. No es necesario decir que esto agrega una sobrecarga adicional y explica por qué los valores resaltados en la pregunta son significativamente más lentos para comparar que otros.
Aquí hay un resumen de las comprobaciones que realiza la función de comparación.
Deja que
v
sea un flotador y échalo como un doble C. Ahora, siw
también es un flotador:Comprueba si
w
esnan
oinf
. Si es así, maneje este caso especial por separado dependiendo del tipo dew
.Si no es así, compare
v
yw
directamente por sus representaciones como C dobles.Si
w
es un entero:Extraer los signos de
v
yw
. Si son diferentes, entonces sabemosv
yw
son diferentes y cuál es el mayor valor.( Los signos son los mismos ) . Verifique si
w
tiene demasiados bits para ser flotante (más desize_t
). Si es así,w
tiene mayor magnitud quev
.Compruebe si
w
tiene 48 o menos bits. Si es así, puede lanzarse con seguridad a un doble C sin perder su precisión y compararlov
.(
w
tiene más de 48 bits. Ahora trataremosw
como un entero positivo después de haber cambiado la operación de comparación según corresponda ) .Considere el exponente de la carroza
v
. Si el exponente es negativo, entoncesv
es menor1
y, por lo tanto, menor que cualquier número entero positivo. De lo contrario, si el exponente es menor que el número de bits,w
entonces debe ser menor quew
.Si el exponente de
v
es mayor que el número de bits,w
entoncesv
es mayor quew
.( El exponente es el mismo que el número de bits
w
) .El cheque final. Dividir
v
en sus partes enteras y fraccionarias. Duplique la parte entera y agregue 1 para compensar la parte fraccionaria. Ahora dobla el número enterow
. Compare estos dos enteros nuevos para obtener el resultado.fuente
Utilizando
gmpy2
números flotantes y enteros de precisión arbitraria es posible obtener un rendimiento de comparación más uniforme:fuente
decimal
en la biblioteca estándar