JavaScript 中使用 parseInt 和位运算做取整操作

Nov 03, 2016 JavaScript https://git.io/vAUit

同步知乎回答:https://www.zhihu.com/question/50856132/answer/123837521

先说结论:

parseInt 更可靠,但是要注意参数的类型和数量;

用位运算符(~~>>0<<0)来“取整”会有一些问题,一般情况不推荐使用;

位运算毕竟是操作符,效率要高一些,如果这个“取整”操作是影响性能的关键操作,并且你知道被操作数不会超出精度限制的情况,可以考虑用位运算

1. parseInt 的用法和问题

Number.parseInt 默认接收两个参数

第一个参数是默认是 string 类型值,如果不是,会通过抽象的 ToString 强制转化成 string 类型的值。这其中就会有强制类型转换过程中的各种坑

第二个参数是 number 类型的进制,如果不是,会通过抽象的 ToNumber 强制转化成 number 类型的值,范围是 2-36,通过强制类型转换后如果是其他值会返回 NaN。在 ES5 之前如果没有传入这个参数,会根据第一个参数的开头来判断进制,0 开头的字符串会判断成八进制,也就是 @貘吃馍香 提到的老黄历坑。ES5 之后已经解决,不传这个参数默认十进制。但是这个参数容易被忽略,尤其是在和 map 之类的也容易忽略后续可选参数的函数搭配使用的时候,比如

1
['10', '10', '10', '10'].map(parseInt) // 结果是 [10, NaN, 2, 3]

所以如果只是用 parseInt 来 “取整”,一个良好的习惯是永远记得设置第二个参数为 10

然后就是尽量不要拿 parseInt 去转换一些其他类型的值,如果实在遇到了需要判断结果(比如一些闲的蛋疼的面试官非得要考察这种)那就先对两个参数进行求值,并转换成相应的类型,然后判断。

判断的过程可以大致理解为:第一个参数的转换结果去除空白,然后从左往右提取出在第二个参数指定的进制下能够理解的整数部分,并返回这个值在十进制下的值。如果转换失败返回 NaN

除了这些情况,parseInt 在 JavaScript 数值允许的范围内都是可以安全使用的

2. >>0<<0 的用法和问题

在看位运算之前首先要明确一点(个人理解,不一定准确):

JavaScript 中的 number 类型的值都是使用 IEEE 754 标准的 64 位双精度浮点型存储,即 1 位符号位 + 11 位指数部分 + 52 位尾数部分 。用来表示整数时,安全的范围是 53 位,超出这个返回可能会造成精度丢失

有一点需要注意的是,位运算会把 NaN、Infinity 当成 0 来处理

参与位运算的操作数都会先对其进行抽象的 ToInt32 操作,规范中最重要的是第三步和第四步:

  1. Let posInt be sign(number) * floor(abs(number)).
  2. Let int32bit be posInt modulo 2^32; that is, a finite integer value k of Number type with positive sign and less than 2^32 in magnitude such that the mathematical difference of posInt and > k is mathematically an integer multiple of 2^32.

简单说就是取整和求余,这里就会有两个问题:

  1. 取整的方式和 Math.floor 不同,ToInt32 是对绝对值取整然后再加符号,Math.floor 是直接向下取整。比如 ToInt32(-1.5) == -1,而 Math.floor(-1.5) == -2

  2. 求余的方式有点复杂,总之就是在某些条件下会发生意想不到的事情(先留个坑,有空了来仔细解读一下)

然后这里的 >>0<<0 对实际的值没有进行具体的位移操作,但是仍然会进行其中的 ToInt32 操作,在一定的范围内就是简单的按绝对值取整,超出这个范围就会变成意想不到的值

3. ~~ 的用法和问题

~ 也是一个位运算符,和上面的位移运算符一样,也会先经过 ToInt32。其作用是将被操作数的二进制形式按位翻转。所以两个 ~~ 连在一起就相当于进行了如下操作:

  1. 对操作数进行 ToInt32

  2. 按位翻转

  3. 再次按位翻转

实际上也还是利用了 ToInt32 的绝对值取整

另外关于 ~ 操作符有个技巧还是比较实用的:

~(-1) 的值为 0,而且 -1 是唯一一个经过 ~ 运算返回假值的值(包括其他那些特殊的值比如 NaN{}[] 等都不会返回假值)

字符串和数组的 indexOf 函数查找失败会返回 -1,这时候就可以用

1
if(~str.indexOf('str')) // 来表示找到了

比判断 >= 0 或者 != -1 更优雅,跟用 !! 来判断非假值有异曲同工之妙

参考资料:

扩展阅读: