When reading Lua’s source code, I noticed that Lua uses a macro to round double
values to 32-bit int
values. The macro is defined in the Llimits.h
header file and reads as follows:
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];}
Here ENDIANLOC
is defined according to endianness: 0 for little endian, 1 for big endian architectures; Lua carefully handles endianness. The t
argument is substituted with an integer type like int
or unsigned int
.
I did a little research and found that there is a simpler format of that macro which uses the same technique:
#define double2int(i, d) \
{double t = ((d) + 6755399441055744.0); i = *((int *)(&t));}
Or, in a C++-style:
inline int double2int(double d)
{
d += 6755399441055744.0;
return reinterpret_cast<int&>(d);
}
This trick can work on any machine using IEEE 754 (which means pretty much every machine today). It works for both positive and negative numbers, and the rounding follows Banker’s Rule. (This is not surprising, since it follows IEEE 754.)
I wrote a little program to test it:
int main()
{
double d = -12345678.9;
int i;
double2int(i, d)
printf("%d\n", i);
return 0;
}
And it outputs -12345679
, as expected.
I would like to understand how this tricky macro works in detail. The magic number 6755399441055744.0
is actually 251 + 252, or 1.5 × 252, and 1.5 in binary can be represented as 1.1. When any 32-bit integer is added to this magic number—
Well, I’m lost from here. How does this trick work?
Update
As @Mysticial points out, this method does not limit itself to a 32-bit
int
, it can also be expanded to a 64-bitint
as long as the number is in the range of 252. (Although the macro needs some modification.)Some materials say this method cannot be used in Direct3D.
When working with Microsoft assembler for x86, there is an even faster macro written in assembly code (the following is also extracted from Lua source):
#define double2int(i,n) __asm {__asm fld n __asm fistp i}
There is a similar magic number for single precision numbers: 1.5 × 223.
ftoi
. But if you're talking SSE, why not just use the single instructionCVTTSD2SI
? – Cory Nelsondouble -> int64
are indeed within the2^52
range. These are particularly common when performing integer convolutions using floating-point FFTs. – Mysticial