前言
今天刷题的时候用到了向下取整,然而js跟java不同,直接用/运算是不会取整的。自然而然想到了一直在用的Math.floor(),今天突然想到了很久之前看到过的~~取整。遂用之。用完之后,想探究下两个运算的区别。
~~取整
简而言之,~~取整的原理是通过~位运算来实现的。
~位运算
~位运算(按位非)是将二进制数按位取反的操作。
- 把运算数转换为 32 位的二进制整数。
- 逐位进行取反操作。
- 把二进制反码转换为十进制浮点数。
举个栗子:
console.log(~3);
// -4
这时候肯定有同学会问: 不是按位取反吗?为什么3取反后不是-3呢?
我个人的理解是这样的:其实这里的取反是指补码的按位取反。(不知道原码补码反码的同学自己回去学习下)
| 十进制 | 原码 | 反码 | 补码 |
|---|---|---|---|
| 3 | 00000011 | 00000011 | 00000011 |
| -4 | 10000100 | 11111011 | 11111100 |
3的补码按位取反后得到正是-4的补码。
我这里为了方便撰写,写成了8位二进制,实际上是32位哦。
限制
位运算符要求它的操作数是整数,这些整数表示为32位整型而不是64位浮点型。必要时,位运算符首先将操作数转换为数字,将数字强制表示为32位整型,这会忽略原格式中的小数部分和任何超过32位的二进制位。 ——《JavaScript权威指南》第六版
上面提到了,是将运算数转换成了32位的二进制数,所以当或者
(第32位是符号位)时,使用~运算得到的结果就是不正确的了。所以只有
时,才能使用~~取整。
let n = 2 ** 31;
console.log(n,~n);
//2147483648 2147483647
//结果异常
n--;
console.log(n,~n);
//2147483647 -2147483648
//结果正常
n = - (2**31)
console.log(n,~n);
//-2147483648 2147483647
//结果正常
n--;
console.log(n,~n);
//-2147483649 -2147483648
//结果异常
从~到~~
~~则是利用了~运算的第一步转换成32位的二进制整数时抹去了小数点后的有效数,从而达成了取整的目的。
console.log(~3.1415926);
// -4
console.log(~-4.396);
// 3
console.log(~~3.1415926);
// 3
console.log(~~-4.396);
// -4
~取整~与Math取整
效果
~~取整时直接抹去了小数点后的有效数,所以当n>=0时,是向下取整,当n<0时,是向上取整。
function foo(n){
if(n>=0){
return Math.floor(n);
}else{
return Math.ceil(n);
}
}
console.log(foo(3.9)==~~3.9);
//true
console.log(foo(-3.8)==~~-3.8);
//true
效率
function foo(){
console.time('floor');
for(let i=0;i<10**8;i++){
Math.floor(i/3);
}
console.timeEnd('floor');
console.time('~~');
for(let i=0;i<10**8;i++){
~~(i/3);
}
console.timeEnd('~~');
}
foo();
//floor: 995.089111328125ms
//~~: 65.19091796875ms
从打印结果明显能看出来位运算比Math库里的方法快很多。
总结
位运算的效率往往是最快的,但是切记要注意使用的正确性。
有任何不对的地方请各位大佬指出来,感激不尽。