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
sumber
ftoi
. Tetapi jika Anda berbicara SSE, mengapa tidak menggunakan instruksi tunggalCVTTSD2SI
?double -> int64
memang dalam2^52
jangkauan. Ini sangat umum ketika melakukan konvolusi bilangan bulat menggunakan FFT titik-mengambang.Jawaban:
A
double
direpresentasikan seperti ini:dan itu dapat dilihat sebagai dua bilangan bulat 32-bit; sekarang, yang
int
diambil di semua versi kode Anda (seandainya itu 32-bitint
) adalah yang di sebelah kanan dalam gambar, jadi apa yang Anda lakukan pada akhirnya hanya mengambil 32 bit mantissa terendah.Sekarang, ke angka ajaib; seperti yang Anda sebutkan dengan benar, 6755399441055744 adalah 2 ^ 51 + 2 ^ 52; menambahkan angka seperti itu memaksa orang
double
untuk masuk ke "rentang manis" antara 2 ^ 52 dan 2 ^ 53, yang, seperti dijelaskan oleh Wikipedia di sini , memiliki properti yang menarik:Ini mengikuti dari fakta bahwa mantissa adalah lebar 52 bit.
Fakta menarik lainnya tentang menambahkan 2 51 +2 52 adalah bahwa ia mempengaruhi mantissa hanya dalam dua bit tertinggi - yang tetap dibuang, karena kami hanya mengambil 32 bit terendahnya.
Terakhir, tanda.
IEEE 754 floating point menggunakan representasi tanda dan magnitudo, sedangkan bilangan bulat pada mesin "normal" menggunakan aritmatika komplemen 2's; bagaimana ini ditangani di sini?
Kami hanya berbicara tentang bilangan bulat positif; sekarang anggaplah kita berhadapan dengan angka negatif dalam kisaran yang diwakili oleh 32-bit
int
, jadi lebih sedikit (dalam nilai absolut) daripada (-2 ^ 31 + 1); sebut itu-a
. Angka seperti itu jelas dibuat positif dengan menambahkan angka ajaib, dan nilai yang dihasilkan adalah 2 52 +2 51 + (- a).Sekarang, apa yang kita dapatkan jika kita menafsirkan mantissa dalam representasi komplemen 2? Itu harus hasil dari jumlah komplemen 2's (2 52 +2 51 ) dan (-a). Sekali lagi, istilah pertama hanya mempengaruhi dua bit bagian atas, yang tersisa dalam bit 0 ~ 50 adalah representasi komplemen 2 dari (-a) (sekali lagi, minus dua bit bagian atas).
Karena pengurangan jumlah komplemen 2 menjadi lebar lebih kecil dilakukan hanya dengan memotong bit tambahan di sebelah kiri, mengambil 32 bit yang lebih rendah memberi kita dengan benar (-a) dalam 32 bit, aritmatika komplemen 2's.
sumber
int64_t
Anda dapat melakukannya dengan menggeser mantissa ke kiri dan kemudian ke kanan dengan 13 bit. Ini akan menghapus eksponen dan dua bit dari angka 'ajaib', tetapi akan menyimpan dan menyebarkan tanda ke seluruh integer bertanda 64-bit.union { double d; int64_t l; } magic; magic.d = input + 6755399441055744.0; magic.l <<= 13; magic.l >>= 13;