Komentarz w kodzie źródłowym Pythona dla obiektów pływających potwierdza, że:
Porównanie to prawie koszmar
Jest to szczególnie prawdziwe, gdy porównujemy liczbę zmiennoprzecinkową z liczbą całkowitą, ponieważ w przeciwieństwie do liczb zmiennoprzecinkowych liczby całkowite w Pythonie mogą być dowolnie duże i zawsze są dokładne. Próba wyrzucenia liczby całkowitej na liczbę zmiennoprzecinkową może stracić precyzję i sprawić, że porównanie będzie niedokładne. Próba rzutowania liczby zmiennoprzecinkowej na liczbę całkowitą również nie zadziała, ponieważ jakakolwiek część ułamkowa zostanie utracona.
Aby obejść ten problem, Python wykonuje serię kontroli, zwracając wynik, jeśli jedna z nich zakończy się powodzeniem. Porównuje znaki dwóch wartości, a następnie czy liczba całkowita jest „zbyt duża”, aby być liczbą zmiennoprzecinkową, a następnie porównuje wykładnik liczby zmiennoprzecinkowej z długością liczby całkowitej. Jeśli wszystkie te testy zakończą się niepowodzeniem, konieczne jest zbudowanie dwóch nowych obiektów Python w celu porównania w celu uzyskania wyniku.
Porównując v
liczbę zmiennoprzecinkową do liczby całkowitej / długiej w
, najgorszym przypadkiem jest:
v
i w
mają ten sam znak (oba dodatnie lub oba ujemne),
- liczba całkowita
w
ma wystarczającą liczbę bitów, aby można ją było zapisać w size_t
typie (zazwyczaj 32 lub 64 bity),
- liczba całkowita
w
ma co najmniej 49 bitów,
- wykładnik pływaka
v
jest taki sam jak liczba bitów w w
.
I właśnie to mamy dla wartości w pytaniu:
>>> import math
>>> math.frexp(562949953420000.7) # gives the float's (significand, exponent) pair
(0.9999999999976706, 49)
>>> (562949953421000).bit_length()
49
Widzimy, że 49 jest zarówno wykładnikiem liczb zmiennoprzecinkowych, jak i liczbą bitów w liczbie całkowitej. Obie liczby są dodatnie, więc cztery powyższe kryteria są spełnione.
Wybranie jednej lub większej wartości (lub mniejszej) może zmienić liczbę bitów liczby całkowitej lub wartość wykładnika, dzięki czemu Python może określić wynik porównania bez przeprowadzania kosztownej kontroli końcowej.
Jest to specyficzne dla implementacji języka CPython.
Porównanie bardziej szczegółowo
float_richcompare
Funkcja obsługuje porównania pomiędzy dwoma wartościami v
i w
.
Poniżej znajduje się opis krok po kroku kontroli, które wykonuje funkcja. Komentarze w źródle Pythona są w rzeczywistości bardzo pomocne, gdy próbujemy zrozumieć, co robi funkcja, więc zostawiłem je tam, gdzie to stosowne. Podsumowałem również te kontrole na liście pod odpowiedzią.
Główną ideą jest mapowanie obiektów Python v
i w
dwóch odpowiednich podwójnych C, i
i j
które można następnie łatwo porównać, aby uzyskać poprawny wynik. Zarówno Python 2, jak i Python 3 używają do tego tych samych pomysłów (poprzedni obsługuje tylko int
i long
typy osobno).
Pierwszą rzeczą, którą należy zrobić, to sprawdzić, że v
jest na pewno pływak Python i mapować go do C podwójnie i
. Następny wygląd Funkcja przy czy w
też pływaka i mapuje je do C podwójnym j
. Jest to najlepszy scenariusz dla tej funkcji, ponieważ wszystkie inne kontrole można pominąć. Funkcja sprawdza również, czy v
jest inf
lub 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;
}
Teraz wiemy, że jeśli w
te testy nie powiodą się, nie jest to pływak w języku Python. Teraz funkcja sprawdza, czy jest liczbą całkowitą w języku Python. W takim przypadku najłatwiejszym sposobem jest wyodrębnienie znakuv
i znaku w
(zwróć, 0
jeśli zero, -1
jeśli ujemne, 1
jeśli dodatnie). Jeśli znaki są różne, to wszystkie informacje potrzebne do zwrócenia wyniku porównania:
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;
}
}
Jeśli to sprawdzenie się nie powiedzie, to v
i w
mieć ten sam znak.
Następne sprawdzenie liczy liczbę bitów w liczbie całkowitej w
. Jeśli ma zbyt wiele bitów, nie może być utrzymywane jako liczba zmiennoprzecinkowa, a zatem musi mieć większą wielkość niż liczba zmiennoprzecinkowa 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;
}
Z drugiej strony, jeśli liczba całkowita w
ma 48 lub mniej bitów, można ją bezpiecznie przekształcić w podwójne C j
i porównać:
if (nbits <= 48) {
j = PyLong_AsDouble(w);
/* It's impossible that <= 48 bits overflowed. */
assert(j != -1.0 || ! PyErr_Occurred());
goto Compare;
}
Od tego momentu wiemy o tym w
ma 49 lub więcej bitów. Wygodnie będzie traktować w
jako dodatnią liczbę całkowitą, więc w razie potrzeby zmień znak i operator porównania:
if (nbits <= 48) {
/* "Multiply both sides" by -1; this also swaps the
* comparator.
*/
i = -i;
op = _Py_SwappedOp[op];
}
Teraz funkcja patrzy na wykładnik pływaka. Przypomnij sobie, że liczba zmiennoprzecinkowa może być zapisana (znak ignorujący) jako znaczenie * 2 wykładnik oraz że znaczenie reprezentuje liczbę między 0,5 a 1:
(void) frexp(i, &exponent);
if (exponent < 0 || (size_t)exponent < nbits) {
i = 1.0;
j = 2.0;
goto Compare;
}
To sprawdza dwie rzeczy. Jeśli wykładnik jest mniejszy niż 0, liczba zmiennoprzecinkowa jest mniejsza niż 1 (a więc mniejsza pod względem wielkości niż jakakolwiek liczba całkowita). Lub, jeśli wykładnik jest mniejszy niż liczba bitów w
, mamy to v < |w|
od znaczenia i * 2 wykładnik jest mniejszy niż 2 nbity .
Niepowodzenie tych dwóch kontroli, funkcja sprawdza, czy wykładnik jest większy niż liczba bitów w w
. To pokazuje, że wykładnik znaczenia i * 2 jest większy niż 2 nbity a zatem v > |w|
:
if ((size_t)exponent > nbits) {
i = 2.0;
j = 1.0;
goto Compare;
}
Jeśli to sprawdzenie się nie powiedzie, wiemy, że wykładnik liczby zmiennoprzecinkowej v
jest taki sam, jak liczba bitów w liczbie całkowitej w
.
Jedynym sposobem, w jaki można teraz porównać te dwie wartości, jest zbudowanie dwóch nowych liczb całkowitych Pythona z v
i w
. Chodzi o to, aby odrzucić część ułamkową v
, podwoić liczbę całkowitą, a następnie dodać jedną. w
jest również podwojony i te dwa nowe obiekty w języku Python można porównać, aby uzyskać poprawną wartość zwracaną. Korzystając z przykładu z małymi wartościami, 4.65 < 4
określa się porównanie(2*4)+1 == 9 < 8 == (2*4)
(zwracanie wartości 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
}
}
Dla zwięzłości pominąłem dodatkowe sprawdzanie błędów i wyrzucanie śmieci, które Python musi zrobić, gdy tworzy te nowe obiekty. Nie trzeba dodawać, że powoduje to dodatkowe obciążenie i wyjaśnia, dlaczego wartości wyróżnione w pytaniu są znacznie wolniejsze w porównaniu z innymi.
Oto podsumowanie kontroli przeprowadzanych przez funkcję porównania.
Niech v
będzie float i rzuć go jako podwójne C. Teraz, jeśli w
jest również zmiennoprzecinkowe:
Sprawdź, czy w
jest nan
lub inf
. Jeśli tak, należy potraktować ten specjalny przypadek osobno, w zależności od rodzaju w
.
Jeśli nie, porównaj v
i w
bezpośrednio przez ich przedstawień jak podwaja C.
Jeśli w
jest liczbą całkowitą:
Wyodrębnij znaki v
i w
. Jeśli są różne, to wiemy v
i w
są różne, a to jest większa wartość.
( Znaki są takie same. ) Sprawdź, czy w
ma zbyt wiele bitów, aby być liczbą zmiennoprzecinkową (więcej niż size_t
). Jeśli tak, w
ma większą wielkość niż v
.
Sprawdź, czy w
ma 48 lub mniej bitów. Jeśli tak, można go bezpiecznie rzucić do podwójnego C bez utraty precyzji i porównać z nim v
.
( w
ma więcej niż 48 bitów. Będziemy teraz traktować w
jako dodatnią liczbę całkowitą, zmieniając odpowiednio operację porównania. )
Rozważ wykładnik pływaka v
. Jeśli wykładnik jest ujemny, to v
jest mniejszy, 1
a zatem mniejszy niż jakakolwiek dodatnia liczba całkowita. W przeciwnym razie, jeśli wykładnik jest mniejszy niż liczba bitów, w
to musi być mniejszy niżw
.
Jeśli wykładnik v
jest większy niż liczba bitów, w
to v
jest większy niżw
.
( Wykładnik jest taki sam jak liczba bitów w w
. )
Ostatnia kontrola. Podziel v
na części całkowite i ułamkowe. Podwój liczbę całkowitą i dodaj 1, aby skompensować część ułamkową. Teraz podwoj liczbę całkowitą w
. Porównaj te dwie nowe liczby całkowite, aby uzyskać wynik.