之前在看 lua 源码的时候,看到一处浮点数转整数的方法,当时确实吓我一跳,后来在网上搜索了才知道浮点数原来还有这么神奇的地方,我看到一篇喜欢的文章,翻译一下(英文一般还请见谅),大家要闲着没事可以看看,先贴出 lua 中的转换方法。

/*
@@ lua_number2int is a macro to convert lua_Number to int.
@@ lua_number2integer is a macro to convert lua_Number to lua_Integer.
** CHANGE them if you know a faster way to convert a lua_Number to
** int (with any rounding method and without throwing errors) in your
** system. In Pentium machines, a naive typecast from double to int
** in C is extremely slow, so any alternative is worth trying.
*/
// 这是这个文件最trick的地方,把一个double数转换成long,用到了神奇的数字6755399441055744.0
/* On a Pentium, resort to a trick */
#if defined(LUA_NUMBER_DOUBLE) && !defined(LUA_ANSI) && !defined(__SSE2__) && \
    (defined(__i386) || defined (_M_IX86) || defined(__i386__))
union luai_Cast { double l_d; long l_l; };
#define lua_number2int(i,d) \
  { volatile union luai_Cast u; u.l_d = (d) + 6755399441055744.0; (i) = u.l_l; }
#define lua_number2integer(i,n)     lua_number2int(i, n)
/* this option always works, but may be slow */
#else
#define lua_number2int(i,d) ((i)=(int)(d))
#define lua_number2integer(i,d) ((i)=(lua_Integer)(d))
#endif


上面用到了一个神奇的数字 6755399441055744.0,通过把一个 double 类型的数加上这个数字再直接拿来用就是整型了。这个真心让我惊奇。于是我马上在我的 vs 里写了个测试了一下,结果同样令我心惊:

120405332.jpg

可以看到我用 8.75 作为测试的 double 数,结果经过 lua 这个宏转换的结果是 9,其实当我把数改成 8.45 时,结果是 8,正好是转换成整数的结果。那么 6755399441055744.0 这个神奇的数字到底是哪里来的呢?

我打开计算器看了一下,发现这个 6755399441055744.0 是 1.5 * 2^52,既然是这么个特殊的数,我们知道在 IEEE 754 里规定双精度浮点数是 64 位,而其中符号位占 1 位,指数部分是 11 位,那么尾数部分就是 52 位,那么这个 1.5 * 2^52 会不会跟这个有关呢?网上一搜看到篇文章,这里翻译了一下,有兴趣了解的朋友可以看看,我这里把文章的意思总结一下,大体意思就是说在做浮点数加减法的时候有这么几个步骤:

1、对阶

(就是使两个浮点数的两个指数部分相同,规定小数向大数对齐,因为如果是大数向小数对齐,那么就要左移大数的尾数部分,就有可能会丢失大数的最高有效位;小数向大数对齐是右移小数的尾数部分,而丢失小数的那点精度对结果来说是无关紧要的)

2、尾数相加

(为什么是相加而不是相加减,因为减法被转成加法做了,这个应该好理解,计算机里只有累加器)

3、对结果规格化

(这个可能比较生疏,其实也很好理解,就像我们十进制的科学记数法一样,我们规定尾数为 [1, 10) 之间的数,二进制里面我们如果尾数不是 1 开头,我们就移动成 1 开头,然后省去存 1,比如计算结果尾数为 0010,那么我就把尾数左移 3 位得到 1000, 然后省去 1,只存 000,当然这个过程指数也会变)

4、舍入处理

(这个不多说,就那个意思)


下面来说这个 6755399441055744.0 的来历,我们利用的就是“对阶”的过程,如果我们把一个双精度浮点数加上 1.5 * 2^52,因为双精度浮点数的尾数部分正好是 52 位,那么在进行对阶的时候,我们想要转换的浮点数如果比 1.5 * 2^52 小,那么就会向 1.5 * 2^52 对齐,