Czytając Lua's kodu źródłowego, zauważyłem, że Lua wykorzystuje macro
się wokół double
do 32-bitowego int
. I wyjął macro
, a wygląda to tak:Szybka metoda zaokrąglić podwójna do 32-bitowego int wyjaśnił
union i_cast {double d; int i[2]};
#define double2int(i, d, t) \
{volatile union i_cast u; u.d = (d) + 6755399441055744.0; \
(i) = (t)u.i[ENDIANLOC];}
Tutaj ENDIANLOC
jest zdefiniowany jako endianness, 0
dla little endian, 1
dla big endian. Lua ostrożnie obchodzi się z endianizmem. t
oznacza typu Integer jak int
lub unsigned int
.
Zrobiłem trochę badań i istnieje prostszy format macro
który wykorzystuje tę samą myśl:
#define double2int(i, d) \
{double t = ((d) + 6755399441055744.0); i = *((int *)(&t));}
Albo w C++ - stylu:
inline int double2int(double d)
{
d += 6755399441055744.0;
return reinterpret_cast<int&>(d);
}
Ta sztuczka może pracować na dowolnym komputerze używając IEEE 754 (co oznacza prawie każdy komputer dzisiaj). Działa zarówno dla liczb dodatnich, jak i ujemnych, a zaokrąglanie następuje po Banker's Rule. (Nie jest to zaskakujące, ponieważ następuje IEEE 754.)
napisałem mały program, aby go przetestować:
int main()
{
double d = -12345678.9;
int i;
double2int(i, d)
printf("%d\n", i);
return 0;
}
I wyprowadza -12345679, jak oczekiwano.
Chciałbym dostać się do szczegółów, jak to skomplikowane macro
prace. Magiczny numer 6755399441055744.0
jest rzeczywiście 2^51 + 2^52
lub 1.5 * 2^52
i 1.5
binarnie może być reprezentowana jako 1.1
. Kiedy do tej magicznej liczby dodana jest jakakolwiek 32-bitowa liczba całkowita, cóż, jestem stąd zagubiony. Jak działa ta sztuczka?
P.S: To jest w kodzie źródłowym Lua, Llimits.h.
UPDATE:
- W @Mysticial wskazuje to sposób nie ogranicza się do 32-bitowego
int
, może być rozszerzony do 64 bitówint
dopóki numer jest w w zakresie 2^52. (Themacro
potrzebuje modyfikacji.) - Niektóre materiały powiedzieć, że sposób ten nie może być stosowany w Direct3D.
Podczas pracy z Microsoft asemblerze dla x86, nie ma nawet szybciej
macro
napisany wassembly
(jest również uzyskiwany z źródła Lua):#define double2int(i,n) __asm {__asm fld n __asm fistp i}
Jest podobny magiczna liczba dla jednego numeru precyzji:
1.5 * 2 ^23
"szybko" w porównaniu do czego? –
@CoryNelson Szybko w porównaniu do prostej obsady. Ta metoda, jeśli jest właściwie zaimplementowana (z wewnętrzną SSE) jest dosłownie sto razy szybsza niż rzutowanie. (która wywołuje nieprzyjemne wywołanie funkcji do dość kosztownego kodu konwersji) – Mysticial
Dobrze - widzę, że jest szybszy niż "ftoi". Ale jeśli mówisz o SSE, dlaczego nie skorzystać z pojedynczej instrukcji 'CVTTSD2SI'? –