Um comentário no código-fonte Python para objetos float reconhece que:
Comparação é praticamente um pesadelo
Isso é especialmente verdadeiro quando se compara um float a um número inteiro, porque, diferentemente dos floats, os números inteiros no Python podem ser arbitrariamente grandes e sempre exatos. Tentar converter o número inteiro em um flutuador pode perder precisão e tornar a comparação imprecisa. Tentar converter o float em um número inteiro também não funcionará porque qualquer parte fracionária será perdida.
Para contornar esse problema, o Python executa uma série de verificações, retornando o resultado se uma delas for bem-sucedida. Ele compara os sinais dos dois valores e, em seguida, se o número inteiro é "muito grande" para ser um ponto flutuante, compara o expoente do ponto flutuante com o comprimento do número inteiro. Se todas essas verificações falharem, é necessário construir dois novos objetos Python para comparar, a fim de obter o resultado.
Ao comparar um float v
com um número inteiro / comprimento w
, o pior caso é o seguinte:
v
e w
tem o mesmo sinal (positivo ou negativo),
- o número inteiro
w
tem poucos bits suficientes para ser retido no size_t
tipo (normalmente 32 ou 64 bits),
- o número inteiro
w
tem pelo menos 49 bits,
- o expoente do flutuador
v
é o mesmo que o número de bits w
.
E é exatamente isso que temos para os valores da pergunta:
>>> import math
>>> math.frexp(562949953420000.7) # gives the float's (significand, exponent) pair
(0.9999999999976706, 49)
>>> (562949953421000).bit_length()
49
Vemos que 49 é o expoente da flutuação e o número de bits no número inteiro. Ambos os números são positivos e, portanto, os quatro critérios acima são atendidos.
A escolha de um dos valores para ser maior (ou menor) pode alterar o número de bits do número inteiro ou o valor do expoente, e assim o Python é capaz de determinar o resultado da comparação sem realizar a verificação final cara.
Isso é específico para a implementação do CPython da linguagem.
A comparação em mais detalhes
A float_richcompare
função lida com a comparação entre dois valores v
e w
.
Abaixo está uma descrição passo a passo das verificações que a função executa. Os comentários na fonte do Python são realmente muito úteis ao tentar entender o que a função faz, então eu os deixei onde relevantes. Também resumi essas verificações em uma lista no pé da resposta.
A idéia principal é mapear os objetos Python v
e w
duas duplicatas C apropriadas, i
e j
, que podem ser facilmente comparadas para fornecer o resultado correto. Tanto o Python 2 quanto o Python 3 usam as mesmas idéias para fazer isso (o primeiro apenas manipula int
e long
digita separadamente).
A primeira coisa a fazer é verificar se v
é definitivamente um flutuador Python e mapeá-lo para um C duplo i
. Em seguida, a função examina se w
também é um flutuador e o mapeia para um C duplo j
. Este é o melhor cenário para a função, pois todas as outras verificações podem ser ignoradas. A função também verifica se v
é 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;
}
Agora sabemos que, se w
essas verificações falharem, não é um flutuador Python. Agora a função verifica se é um número inteiro Python. Se for esse o caso, o teste mais fácil é extrair o sinal de v
e o sinal de w
(retornar 0
se zero, -1
se negativo, 1
se positivo). Se os sinais forem diferentes, essas são todas as informações necessárias para retornar o resultado da comparação:
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 esta verificação falhar, em seguida, v
e w
têm o mesmo sinal.
A próxima verificação conta o número de bits no número inteiro w
. Se tiver muitos bits, não poderá ser mantido como um flutuador e, portanto, deve ser maior em magnitude do que o flutuador 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;
}
Por outro lado, se o número inteiro w
tiver 48 ou menos bits, ele pode transformar com segurança um C duplo j
e comparar:
if (nbits <= 48) {
j = PyLong_AsDouble(w);
/* It's impossible that <= 48 bits overflowed. */
assert(j != -1.0 || ! PyErr_Occurred());
goto Compare;
}
Desse ponto em diante, sabemos que w
possui 49 ou mais bits. Será conveniente tratar w
como um número inteiro positivo; portanto, altere o sinal e o operador de comparação conforme necessário:
if (nbits <= 48) {
/* "Multiply both sides" by -1; this also swaps the
* comparator.
*/
i = -i;
op = _Py_SwappedOp[op];
}
Agora a função olha para o expoente do flutuador. Lembre-se de que um float pode ser gravado (sinal de ignição) como expoente significand * 2 e que o significand representa um número entre 0,5 e 1:
(void) frexp(i, &exponent);
if (exponent < 0 || (size_t)exponent < nbits) {
i = 1.0;
j = 2.0;
goto Compare;
}
Isso verifica duas coisas. Se o expoente for menor que 0, o flutuador será menor que 1 (e, portanto, menor em magnitude que qualquer número inteiro). Ou, se o expoente for menor que o número de bits, w
então temos que, v < |w|
já que o significante * 2 expoente é menor que 2 nbits .
Na falta dessas duas verificações, a função procura ver se o expoente é maior que o número de bits w
. Isso mostra que o significante * 2 expoente é maior que 2 nbits e, portanto v > |w|
:
if ((size_t)exponent > nbits) {
i = 2.0;
j = 1.0;
goto Compare;
}
Se essa verificação não for bem-sucedida, sabemos que o expoente da flutuação v
é o mesmo que o número de bits no número inteiro w
.
A única maneira de comparar os dois valores agora é construir dois novos inteiros Python a partir de v
e w
. A idéia é descartar a parte fracionária de v
, dobrar a parte inteira e adicionar uma. w
também é duplicado e esses dois novos objetos Python podem ser comparados para fornecer o valor de retorno correto. Usando um exemplo com valores pequenos, 4.65 < 4
seria determinado pela comparação (2*4)+1 == 9 < 8 == (2*4)
(retornando 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
}
}
Por uma questão de brevidade, deixei de lado a verificação de erros e o rastreamento de lixo adicionais que o Python precisa fazer quando cria esses novos objetos. Escusado será dizer que isso adiciona uma sobrecarga adicional e explica por que os valores destacados na pergunta são significativamente mais lentos em comparação do que outros.
Aqui está um resumo das verificações que são executadas pela função de comparação.
Let v
Ser um float e lançá-lo como um C duplo. Agora, se w
também é um float:
Verifique se w
é nan
ou inf
. Nesse caso, lide com este caso especial separadamente, dependendo do tipo de w
.
Caso contrário, compare v
e w
diretamente por suas representações, enquanto C dobra.
Se w
é um número inteiro:
Extraia os sinais de v
e w
. Se são diferentes, sabemos v
e w
somos diferentes e qual é o maior valor.
( Os sinais são os mesmos. ) Verifique se w
há bits demais para ser um flutuador (mais que size_t
). Se sim, w
tem uma magnitude maior que v
.
Verifique se w
possui 48 ou menos bits. Nesse caso, ele pode ser convertido com segurança em um duplo C sem perder a precisão e comparado com v
.
( w
possui mais de 48 bits. Agora, trataremos w
como um número inteiro positivo depois de alterar a comparação, conforme apropriado. )
Considere o expoente do flutuador v
. Se o expoente for negativo, v
será menor que 1
e, portanto, menor que qualquer número inteiro positivo. Caso w
contrário , se o expoente for menor que o número de bits , deverá ser menor que w
.
Se o expoente de v
for maior que o número de bits, w
então v
será maior que w
.
( O expoente é igual ao número de bits w
. )
A verificação final. Divida v
em partes inteiras e fracionárias. Dobre a parte inteira e adicione 1 para compensar a parte fracionária. Agora dobre o número inteiro w
. Compare esses dois novos números inteiros para obter o resultado.