一个快速的方法来解释一个double到一个32位整数
当读Lua的源代码的时候,我注意到Lua使用一个macro
来将一个double
加到一个32位int
。 我提取macro
,它看起来像这样:
union i_cast {double d; int i[2]}; #define double2int(i, d, t) \ {volatile union i_cast u; ud = (d) + 6755399441055744.0; \ (i) = (t)ui[ENDIANLOC];}
这里ENDIANLOC
被定义为字节序 , 0
表示小端, 1
表示大端。 Lua仔细处理sorting。 t
表示整数types,如int
或unsigned int
。
我做了一点研究,有一个更简单的macro
格式,使用相同的想法:
#define double2int(i, d) \ {double t = ((d) + 6755399441055744.0); i = *((int *)(&t));}
或者以C ++风格:
inline int double2int(double d) { d += 6755399441055744.0; return reinterpret_cast<int&>(d); }
这个技巧可以在任何使用IEEE 754的机器上工作(这意味着今天几乎所有的机器)。 它适用于正面和负面的数字,四舍五入遵循银行家的规则 。 (这并不令人惊讶,因为它遵循IEEE 754.)
我写了一个小程序来testing它:
int main() { double d = -12345678.9; int i; double2int(i, d) printf("%d\n", i); return 0; }
并按预期输出-12345679。
我想详细了解这个棘手的macro
如何工作。 幻数6755399441055744.0
实际上是2^51 + 2^52
或者1.5 * 2^52
,二进制中的1.5
可以表示为1.1
。 当任何32位整数加到这个魔数上,好吧,我从这里输了。 这个技巧是如何工作的?
PS:这是在Lua源代码Llimits.h中 。
更新 :
- 正如@Mysticial所指出的那样,这个方法并不局限于一个32位的
int
,只要数字在2 ^ 52的范围内,它也可以被扩展为一个64位的int
。 (macro
需要修改。) - 有些材料说这个方法不能用在Direct3D中 。
-
当使用x86的Microsoft汇编程序时,汇编中写入了更快的
macro
(这也是从Lua源代码中提取的):#define double2int(i,n) __asm {__asm fld n __asm fistp i}
-
单精度数有一个相似的幻数:
1.5 * 2 ^23
一个double
代表是这样的:
它可以看作是两个32位整数; 现在,在你的代码的所有版本(假设它是一个32位int
)中的int
是图中右边的那个,所以你最后做的只是尾数的最低32位。
现在,以魔术数字; 正如你所说的,6755399441055744是2 ^ 51 + 2 ^ 52; 加上这样的数字迫使double
进入2 ^ 52和2 ^ 53之间的“甜蜜范围”,正如维基百科解释的, 这里有一个有趣的属性:
在2 52 = 4,503,599,627,370,496和2 53 = 9,007,199,254,740,992之间,可表示的数字正好是整数
这是由于尾数为52位的事实。
关于增加2 51 +2 52的另一个有趣的事实是,它只影响尾数的两个最高位,因为我们只取最低的32位。
最后但并非最不重要的一个标志。
IEEE 754浮点使用幅度和符号表示,而“正常”机器上的整数使用2的补码运算; 这是怎么处理的?
我们只讨论正整数。 现在假设我们正在处理可由32位int
表示的范围内的负数,所以小于(-2 ^ 31 + 1)的绝对值。 叫它-a
。 这样的数字显然是通过加上幻数而变成正数,得到的数值是2 52 +2 51 +( – a)。
现在,如果我们解释2的补码表示中的尾数,我们会得到什么? 它必须是(2 52 +2 51 )和(-a)的2的补数和的结果。 同样,第一项只影响高两位,在0〜50位保留的是(-a)的二进制补码表示(再次,减去高两位)。
由于将2的补数减less到一个较小的宽度只需切掉左边的多余的位就可以了,取最低的32位给了我们正确的(-a)32位补码algorithm。