Czytając kod źródłowy Lua , zauważyłem, że Lua używa a, macroaby zaokrąglić a doubledo 32-bitowego int. Wyodrębniłem macroi wygląda to tak:
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 ENDIANLOCdefiniuje się jako endianness , 0dla little endian, 1dla big endian. Lua ostrożnie radzi sobie z endianizmem. toznacza typ liczby całkowitej, na przykład intlub unsigned int.
Zrobiłem trochę badań i istnieje prostszy format, macroktóry wykorzystuje tę samą myśl:
#define double2int(i, d) \
{double t = ((d) + 6755399441055744.0); i = *((int *)(&t));}
Lub w stylu C ++:
inline int double2int(double d)
{
d += 6755399441055744.0;
return reinterpret_cast<int&>(d);
}
Ta sztuczka może działać na każdej maszynie używającej IEEE 754 (co oznacza dzisiaj prawie każdą maszynę). Działa zarówno dla liczb dodatnich, jak i ujemnych, a zaokrąglanie odbywa się zgodnie z regułą bankiera . (Nie jest to zaskakujące, ponieważ jest zgodne z IEEE 754.)
Napisałem mały program do testowania:
int main()
{
double d = -12345678.9;
int i;
double2int(i, d)
printf("%d\n", i);
return 0;
}
I wyprowadza -12345679, zgodnie z oczekiwaniami.
Chciałbym szczegółowo wyjaśnić, jak macrodziała ta sztuczka . Magiczna liczba 6755399441055744.0to w rzeczywistości 2^51 + 2^52lub 1.5 * 2^52, 1.5aw systemie dwójkowym może być reprezentowana jako 1.1. Kiedy do tej magicznej liczby zostanie dodana jakakolwiek 32-bitowa liczba całkowita, cóż, zgubiłem się stąd. Jak działa ta sztuczka?
PS: To jest w kodzie źródłowym Lua, Llimits.h .
AKTUALIZACJA :
- Jak wskazuje @Mysticial, ta metoda nie ogranicza się do wersji 32-bitowej
int, można ją również rozszerzyć do wersji 64-bitowej,into ile liczba mieści się w zakresie 2 ^ 52. (macroPotrzebuje pewnych modyfikacji.) - Niektóre materiały mówią, że ta metoda nie może być używana w Direct3D .
Podczas pracy z asemblerem Microsoft dla x86, jest jeszcze szybszy
macrozapisassembly(jest to również wyodrębnione ze źródła Lua):#define double2int(i,n) __asm {__asm fld n __asm fistp i}Istnieje podobna liczba magiczna dla liczby pojedynczej precyzji:
1.5 * 2 ^23
ftoi. Ale jeśli mówisz o SSE, dlaczego nie skorzystać po prostu z jednej instrukcji CVTTSD2SI?
double -> int64rzeczywiście mieści się w 2^52zakresie. Są one szczególnie powszechne podczas wykonywania zwojów całkowitych przy użyciu zmiennoprzecinkowych FFT.
