Porównując liczby zmiennoprzecinkowe z liczbami całkowitymi, ocena niektórych par zajmuje znacznie więcej czasu niż innych wartości o podobnej wielkości.
Na przykład:
>>> import timeit
>>> timeit.timeit("562949953420000.7 < 562949953421000") # run 1 million times
0.5387085462592742
Ale jeśli liczba zmiennoprzecinkowa lub liczba całkowita zostanie zmniejszona lub powiększona o określoną wartość, porównanie przebiega znacznie szybciej:
>>> timeit.timeit("562949953420000.7 < 562949953422000") # integer increased by 1000
0.1481498428446173
>>> timeit.timeit("562949953423001.8 < 562949953421000") # float increased by 3001.1
0.1459577925548956
Zmiana operatora porównania (np. Użycie ==
lub >
zamiast tego) nie wpływa na czasy w zauważalny sposób.
Nie jest to związane wyłącznie z wielkością, ponieważ wybieranie większych lub mniejszych wartości może skutkować szybszymi porównaniami, więc podejrzewam, że jest to spowodowane niefortunnym układem bitów.
Oczywiście porównanie tych wartości jest wystarczająco szybkie dla większości przypadków użycia. Jestem po prostu ciekawy, dlaczego Python wydaje się walczyć bardziej z niektórymi parami wartości niż z innymi.
źródło
Odpowiedzi:
Komentarz w kodzie źródłowym Pythona dla obiektów pływających potwierdza, że:
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ługiejw
, najgorszym przypadkiem jest:v
iw
mają ten sam znak (oba dodatnie lub oba ujemne),w
ma wystarczającą liczbę bitów, aby można ją było zapisać wsize_t
typie (zazwyczaj 32 lub 64 bity),w
ma co najmniej 49 bitów,v
jest taki sam jak liczba bitów ww
.I właśnie to mamy dla wartości w pytaniu:
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ściamiv
iw
.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
iw
dwóch odpowiednich podwójnych C,i
ij
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 tylkoint
ilong
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ójniei
. Następny wygląd Funkcja przy czyw
też pływaka i mapuje je do C podwójnymj
. Jest to najlepszy scenariusz dla tej funkcji, ponieważ wszystkie inne kontrole można pominąć. Funkcja sprawdza również, czyv
jestinf
lubnan
: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 znakuw
(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:Jeśli to sprawdzenie się nie powiedzie, to
v
iw
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 zmiennoprzecinkowav
:Z drugiej strony, jeśli liczba całkowita
w
ma 48 lub mniej bitów, można ją bezpiecznie przekształcić w podwójne Cj
i porównać: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: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:
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 tov < |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 zatemv > |w|
:Jeśli to sprawdzenie się nie powiedzie, wiemy, że wykładnik liczby zmiennoprzecinkowej
v
jest taki sam, jak liczba bitów w liczbie całkowitejw
.Jedynym sposobem, w jaki można teraz porównać te dwie wartości, jest zbudowanie dwóch nowych liczb całkowitych Pythona z
v
iw
. 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).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śliw
jest również zmiennoprzecinkowe:Sprawdź, czy
w
jestnan
lubinf
. Jeśli tak, należy potraktować ten specjalny przypadek osobno, w zależności od rodzajuw
.Jeśli nie, porównaj
v
iw
bezpośrednio przez ich przedstawień jak podwaja C.Jeśli
w
jest liczbą całkowitą:Wyodrębnij znaki
v
iw
. Jeśli są różne, to wiemyv
iw
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 nimv
.(
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, tov
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
tov
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.źródło
Używając
gmpy2
dowolnych liczb zmiennoprzecinkowych i liczb całkowitych, można uzyskać bardziej jednolite wyniki porównania:źródło
decimal
w standardowej bibliotece