这两天在写 Electron 的时候发现了一个很诡异的错误。程序前两天测试的时候没有问题,今天突然不行了。检查代码发现错误原因是这样的:

1
buf.writeUint32BE(now & 0xffffffff, idx)

其中buf是一个Buffer对象,now是一个时间戳,为一个较大的整数。这行代码的目的是将时间戳的低 32 位写入 buf。不过,当now的第三十二位(自低向高)为 1 时,now & 0xffffffff出来的会是负数,这样在writeUint32BE的检查中就会报错,因为这个函数不允许写入负数。

now的位数显然超过了 32 比特,如果在一些类型比较强的语言里面,应该会被视为一个64位整数。那么做位运算之后应该还是一个64位整数。上面这个错误说明在Js中,一个64位整型的数字在位运算之后得到的却是一个32位有符号数。是否是0xffffffff限制了输出的位数呢?

1
2
3
4
> a = 0xffffffff
68719476735
> a & 0x00000000ffffffff
-1

0xffffffff 当做32位有符号数就是-1

可见我们用64位的mask去做位运算得到的还是32位有符号数。

这种乱象的根本原因是,js实际上是使用 IEEE-754 浮点数来表示所有的数字的,包括整型也是用浮点数来表示的。另外,在js中,位运算的输出总是被当做有符号32位数字source。为了摒除这两个因素的影响,我们就无法用位运算来提取低32位的内容,而应该转而使用取余运算:

1
2
3
4
5
6
> > a = 0xffffffff
68719476735
> mask = Math.pow(2, 32)
4294967296
> lower_32_bits = a % mask
4294967295

其中4294967296 = 0x1000000000, 4294967295=0xffffffff