Czytając kod źródłowy Lua , zauważyłem, że Lua używa a, macro
aby zaokrąglić a double
do 32-bitowego int
. Wyodrębniłem macro
i 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 ENDIANLOC
definiuje się jako endianness , 0
dla little endian, 1
dla big endian. Lua ostrożnie radzi sobie z endianizmem. t
oznacza typ liczby całkowitej, na przykład 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));}
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 macro
działa ta sztuczka . Magiczna liczba 6755399441055744.0
to w rzeczywistości 2^51 + 2^52
lub 1.5 * 2^52
, 1.5
aw 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,int
o ile liczba mieści się w zakresie 2 ^ 52. (macro
Potrzebuje 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
macro
zapisassembly
(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 -> int64
rzeczywiście mieści się w 2^52
zakresie. Są one szczególnie powszechne podczas wykonywania zwojów całkowitych przy użyciu zmiennoprzecinkowych FFT.