记一次js中位运算遇到的坑

364 阅读2分钟

前言

原因是在刷 leetcode 的时候因为位运算导致的期待值错误死活无法通过,也增加了一些js中关于位运算的知识。

首先先回顾一下自己所遇到的问题,在使用迭代法解答剑指 Offer 16. 数值的整数次方问题时,发现对于一个测试用例超时:1.00000 -2147483648

本来的解答是:

function myPow(x: number, n: number): number {
    let ans = 1;
    let exponent = Math.abs(n);
    for (let i = exponent; i != 0; i >>= 1, x *= x) {
        if (i % 2) {
            ans *= x
        }
    }
    return n < 0 ? 1 / ans : ans;
};

于是在循环中加入了一条 console.log(i) 发现 i 变成了负数,最后一直是 -1,导致死循环。

WTF???咋回事。

于是控制台手动计算:

2147483648 >> 1

怎么会这样,******!

但是 num / 2 没问题啊,就先看看2进制吧:

num.toString(2)
// "10000000000000000000000000000000"
num.toString(2).length
// 32

要变成负数,这是无符号右移,说明符号位是1,可是这符号位不是1啊,毕竟 js 中是以 64 位双精度存储。

老老实实先用除法解决问题吧。

后来经过一番搜索,发现自己还是大意了,没看过标准还是得吃亏,es 标准说 js 中的位运算针对的都是 32 位整型,也就是说 js 在进行位运算操作时,先会将数字转为 32 位的整型,然后再进行位运算操作。这也就很清楚的解释了刚才所遇到的问题。

The production A : A @ B, where @ is one of the bitwise operators in the productions above, is evaluated as follows:

  1. Let lref be the result of evaluating A.
  2. Let lval be GetValue(lref).
  3. Let rref be the result of evaluating B.
  4. Let rval be GetValue(rref).
  5. Let lnum be ToInt32(lval).
  6. Let rnum be ToInt32(rval).
  7. Return the result of applying the bitwise operator @ to lnum and rnum. The result is a signed 32 bit integer.

注: 第5和第6步清除的说明了情况。

先前只知道 js 位运算需要先转换为整数,但是没想到是 32 位,一直天真的认为是 64 位,这下长记性了。

结论

没有什么必要在 js 中使用位运算,未必就能提高性能,引擎未必不会帮我们优化,由于 number 类型存储的方式,就可能会踩坑。

参考资料