Ketika membaca kode sumber Lua , saya perhatikan bahwa Lua menggunakan a macro
untuk membulatkan a double
ke 32-bit int
. Saya mengekstraknya macro
, dan terlihat seperti ini:
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];}
Di sini ENDIANLOC
didefinisikan sebagai endianness , 0
untuk little endian, 1
untuk big endian. Lua dengan hati-hati menangani endianness. t
singkatan dari tipe integer, seperti int
atau unsigned int
.
Saya melakukan sedikit riset dan ada format yang lebih sederhana macro
yang menggunakan pemikiran yang sama:
#define double2int(i, d) \
{double t = ((d) + 6755399441055744.0); i = *((int *)(&t));}
Atau dalam gaya C ++ -:
inline int double2int(double d)
{
d += 6755399441055744.0;
return reinterpret_cast<int&>(d);
}
Trik ini dapat bekerja pada mesin apa pun menggunakan IEEE 754 (yang berarti hampir semua mesin saat ini). Ini bekerja untuk angka positif dan negatif, dan pembulatannya mengikuti Aturan Banker . (Ini tidak mengejutkan, karena mengikuti IEEE 754.)
Saya menulis sebuah program kecil untuk mengujinya:
int main()
{
double d = -12345678.9;
int i;
double2int(i, d)
printf("%d\n", i);
return 0;
}
Dan output -12345679, seperti yang diharapkan.
Saya ingin menjelaskan bagaimana cara macro
kerja rumit ini . Angka ajaib 6755399441055744.0
sebenarnya 2^51 + 2^52
, atau 1.5 * 2^52
, dan 1.5
dalam biner dapat direpresentasikan sebagai 1.1
. Ketika bilangan bulat 32-bit ditambahkan ke angka ajaib ini, well, saya hilang dari sini. Bagaimana trik ini bekerja?
PS: Ini dalam kode sumber Lua, Llimits.h .
PEMBARUAN :
- Seperti yang ditunjukkan oleh @Mysticial, metode ini tidak membatasi dirinya sendiri menjadi 32-bit
int
, tetapi juga dapat diperluas menjadi 64-bitint
selama angkanya berada dalam kisaran 2 ^ 52. (macro
Perlu beberapa modifikasi.) - Beberapa bahan mengatakan metode ini tidak dapat digunakan dalam Direct3D .
Ketika bekerja dengan Microsoft assembler untuk x86, ada yang lebih cepat
macro
ditulisassembly
(ini juga diekstrak dari sumber Lua):#define double2int(i,n) __asm {__asm fld n __asm fistp i}
Ada angka ajaib serupa untuk nomor presisi tunggal:
1.5 * 2 ^23
ftoi
. Tetapi jika Anda berbicara SSE, mengapa tidak menggunakan instruksi tunggal CVTTSD2SI
?
double -> int64
memang dalam 2^52
jangkauan. Ini sangat umum ketika melakukan konvolusi bilangan bulat menggunakan FFT titik-mengambang.