1、按位非:操作数的负值减一
2、位操作符包含,与,或,非,异或,左移、有符号右移,无符号右移
简介
JS中的所有数值都是以IEEE 754 64位格式存储,但是位操作并不直接对64位数值进行操作的,而是先把数值转换位32位整数,再进行对应的运算,之后再把结果转换位64位,因此我们在进行位运算的时候,就只需要考虑32位整数就行了。
按位非NOT(~)
-
按位非的作用是对位取反,就是1变成0,0变成1,跟取二进制反码的差别是反码符号位不变,而按位非的符号位也要取反。
-
按位非的最终结果始终对应的是~x = (-x) - 1;
使用场景:取整
按位非可以用来取整:如~~3.14 = 3;因为位运算操作的是整数,会忽略掉小数部分;
按位与AND(&)
- 按位与需要两个操作数,在两个数对应位都是1的时候才返回1
例1:25 & 3
25 = 0000 0000 0000 0000 0000 0000 0001 1001
3 = 0000 0000 0000 0000 0000 0000 0000 0011
--------------------------------------------
& = 0000 0000 0000 0000 0000 0000 0000 0001
result:25 & 3 = 1
例2:-25 & 3
-25 = 1111 1111 1111 1111 1111 1111 1110 0111
3 = 0000 0000 0000 0000 0000 0000 0000 0011
---------------------------------------------
& = 0000 0000 0000 0000 0000 0000 0000 0011
result:3
// 注意负数要先求补码再进行位运算
复制代码
使用场景1: 判断奇偶数
奇数 & 1 // 1
偶数 & 1 // 0
// 因为1的二进制只有最后一位为1,其余都是0,而偶数的二进制最后一位必为0,奇数最后一位必为1。
复制代码
使用场景2: 判断是否为2的整数幂
可以通过 n & (n - 1) 来判断
let a = 20;
let b = 32;
a & (a - 1) // 16 a不是2的整数幂
b & (b - 1) // 0 b是2的整数幂
复制代码
如下所示,2的整数幂的数的二进制都是1后面跟若干个0,那么当这个数减去1(0000 0001)之后,原本1的那一位就会变为0而原本1后面的就会变成1,这样再使用&,得到的结果就一定为0
0000 0001 -> 1 // 2^0
0000 0010 -> 2 // 2^1
0000 0100 -> 4 // 2^2
0000 1000 -> 8 // 2^3
复制代码
这个方法能用于解决 leetcode的231题。
按位或OR(|)
- 按位或是将两个操作数的二进制的每一位进行对比,只要其中一个为1就返回1,否则返回0;
18 | 5
18 -> 0001 0010
5 -> 0000 0110
22 -> 0001 0110
复制代码
使用场景1: 取整
1.11 | 0 // 1
2.2542 | 0 // 2
// 主要还是利用位运算操作的是整数这一点来进行取整
复制代码
按位异或XOR(^)
- 按位异或是将两个操作数的二进制的每一位进行对比,当两个数不一样时,返回1,否则返回0;
例1:25 ^ 3
25 = 0000 0000 0000 0000 0000 0000 0001 1001
3 = 0000 0000 0000 0000 0000 0000 0000 0011
--------------------------------------------
& = 0000 0000 0000 0000 0000 0000 0001 1010
result:25 ^ 3 = 1
复制代码
使用场景1: 取整
1.11 ^ 0 // 1
2.2542 ^ 0 // 2
// 同上,主要还是位运算操作的是整数这一点来进行取整
复制代码
使用场景2: 比较两个整数(或小数的整数部分)是否相等
1 ^ 1 // 0
5 ^ 5 // 0
1.23 ^ 1.432 // 0
2.34 ^ 3.34 // 1
复制代码
使用场景3: 完成值交换
let a = 1;
let b = 2;
a = a ^ b;
b = b ^ a;
a = a ^ b;
console.log(a, b) // 2, 1
// 可以这么用,但不如解构赋值方便
复制代码
使用场景4: 判断两个数符号是否相同
var a = 5;
var b = 2;
var c = -3;
console.log((a ^ b) >= 0); // true
console.log((a ^ c) >= 0); // false
复制代码
主要原理还是对他的符号位进行按位异或操作,如果符号位都是0,就
返回0
,此时得到的数字必定大于等于0,如果符号位一个为0,一个为1,返回1
,此时得到的数字必定小于等于0,最后若符号位都为1,则返回0
,结果也是大于等于0的
左移(<<)
- 将数值的二进制码按照指定的位数向左移动,符号位不变
例:2 << 5
2 -> 0000 0010
0100 0000
--------------
result: 64;
复制代码
左移的计算规则可以总结为:x << y =
x * 2^y
; 使用场景1:取整
1.11 << 0 // 1
2.2542 << 0 // 2
复制代码
左移也可以用来取整,同时,下面的
有符号右移(>>)
和无符号右移(>>>)
也可以用来取整。
有符号右移(>>)
- 将数值的二进制码按照指定的位数向右移动,符号位不变
例1:2 >> 5
2 -> 0000 0010
0000 0000
--------------
这里的2转换成二进制后向右移动5位被自动省略了,所以:最终结果是0;
例2:64 >> 5
2 -> 0100 0000
0000 0010
--------------
result: 2;
复制代码
有符号右移的计算规则可以总结为:x >> y =
x / 2^y
;
无符号右移(>>>)
- 将数值的二进制码按照指定的位数向右移动,符号位也会跟着移动,整数在无符号右移的情况下跟有符号右移的结果是相同的,但是负数的话因为符号位也会右移,有时候会导致最终结果非常大。
例:-1 >>> 1
-1 -> 1111 1111 1111 1111 1111 1111 1111 1111
0111 1111 1111 1111 1111 1111 1111 1111
---------------
result: 2147483647
复制代码
使用位运算进行权限管理
- 使用
左移(<<)
创建权限
const test1 = 1 << 0 // 以8位二进制表示:00000001
const test2 = 1 << 1 // 00000010
const test3 = 1 << 2 // 00000100
const test4 = 1 << 3 // 00001000
const test5 = 1 << 4 // 00010000
const test6 = 1 << 5 // 00100000
复制代码
- 使用
按位或(|)
分配权限
const user1 = test1; // 00000001
const user2 = test1 | test2; // 00000011
const user3 = test1 | test2 | test3; // 00000111
const user4 = test1 | test3 | test4; // 00011001
const user5 = test6 | test3 | test4 | test5; // 00111100
复制代码
3.使用按位与(&)
校验权限
console.log((user1 & test1) > 0) // true;
console.log((user5 & test6) > 0) // true;
console.log((user5 & test1) > 0) // false;
console.log((user3 & test2) > 0) // true;
复制代码
JS的位运算要比普通运算更快吗
JS中的位运算并不一定会比较快,因为js引擎对位运算是模拟的,不是直接调用cpu进行运算的,和C语言等其他底层语言不一样,这取决于JS引擎的优化,目前来说,两种运算方式差不多处于同个量级。可运行如下代码查看一下:
console.time('test1');
console.log(Math.round(-3.434));
console.timeEnd('test1');
console.time('test2');
console.log(~~-3.434);
console.timeEnd('test2');
复制代码
总结一下
- 位运算都是先将数值转换成32位二进制进行操作的。
- JS中位运算并不一定会比普通运算更快。
- 取反(
~
),按位或(|
),按位异或(^
),左移(<<
),有符号右移(>>
),无符号右移(>>>
)都可以对小数取整,取整原理就是位运算只操作32为整数,忽略小数部分。 - 在对
NaN
和Infinity
进行位运算时,这两个值会被当成0处理。 - 对不是number类型的数据进行位运算时,会先调用Number()方法进行转换,再进行操作。