Khi đọc mã nguồn của Lua , tôi nhận thấy rằng Lua sử dụng a macro
để làm tròn từ a double
đến 32 bit int
. Tôi đã trích xuất macro
, và nó trông như thế này:
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];}
Ở đây ENDIANLOC
được định nghĩa là endianness , 0
cho endian nhỏ, 1
cho endian lớn. Lua cẩn thận xử lý endianness. t
là viết tắt của kiểu số nguyên, như int
hoặc unsigned int
.
Tôi đã làm một nghiên cứu nhỏ và có một định dạng đơn giản hơn macro
sử dụng cùng một suy nghĩ:
#define double2int(i, d) \
{double t = ((d) + 6755399441055744.0); i = *((int *)(&t));}
Hoặc theo kiểu C ++:
inline int double2int(double d)
{
d += 6755399441055744.0;
return reinterpret_cast<int&>(d);
}
Thủ thuật này có thể hoạt động trên mọi máy sử dụng IEEE 754 (có nghĩa là khá nhiều máy hiện nay). Nó hoạt động cho cả số dương và số âm và làm tròn theo Quy tắc của Ngân hàng . (Điều này không gây ngạc nhiên, vì nó tuân theo IEEE 754.)
Tôi đã viết một chương trình nhỏ để kiểm tra nó:
int main()
{
double d = -12345678.9;
int i;
double2int(i, d)
printf("%d\n", i);
return 0;
}
Và nó xuất ra -12345679, như mong đợi.
Tôi muốn đi vào chi tiết cách thức macro
hoạt động của mánh khóe này . Số ma thuật 6755399441055744.0
thực sự là 2^51 + 2^52
, hoặc 1.5 * 2^52
, và 1.5
trong nhị phân có thể được biểu diễn dưới dạng 1.1
. Khi bất kỳ số nguyên 32 bit nào được thêm vào số ma thuật này, tôi sẽ bị mất từ đây. Thủ thuật này hoạt động như thế nào?
PS: Đây là mã nguồn Lua, Llimits.h .
CẬP NHẬT :
- Như @Mysticial chỉ ra, phương pháp này không giới hạn ở mức 32 bit
int
, nó cũng có thể được mở rộng thành 64 bitint
miễn là con số nằm trong phạm vi 2 ^ 52. (macro
Cần một số sửa đổi.) - Một số tài liệu nói rằng phương pháp này không thể được sử dụng trong Direct3D .
Khi làm việc với trình biên dịch Microsoft cho x86, thậm chí còn
macro
được viết nhanh hơnassembly
(điều này cũng được trích xuất từ nguồn Lua):#define double2int(i,n) __asm {__asm fld n __asm fistp i}
Có một số ma thuật tương tự cho số chính xác duy nhất:
1.5 * 2 ^23
ftoi
. Nhưng nếu bạn đang nói SSE, tại sao không sử dụng chỉ dẫn duy nhất CVTTSD2SI
?
double -> int64
thực sự nằm trong 2^52
phạm vi. Điều này đặc biệt phổ biến khi thực hiện các kết hợp số nguyên bằng cách sử dụng các FFT dấu phẩy động.