众所周知,位运算是我们学计算机必学的东西,平时的数组运算,其实是要先转换成二进制再进行运算的,而位运算就是直接进行二进制运算,位运算是低级的运算操作,所以速度往往也是最快的(相对其它运算如加减乘除来说),并且借助位运算的特性还能实现一些算法。恰当地使用运算有很多好处,前人用二进制、位运算给我们了一个操作简单的计算机,但我们却很少接触位运算了。所有的位运算都是在二进制下来进行运算的,再二进制下只有0和1。
下面通过一些实例来加深对位运算的理解。
二进制是计算技术中广泛采用的一种数制。二进制数据是用0和1两个数码来表示的数。它的基数为2,进位规则是“逢二进一”,借位规则是“借一当二”,由18世纪德国数理哲学大师莱布尼兹发现。当前的计算机系统使用的基本上是二进制系统,数据在计算机中主要是以补码的形式存储的。计算机中的二进制则是一个非常微小的开关,用“开”来表示1,“关”来表示0。
运算符 | 含义 | 举例 | 规则 |
---|---|---|---|
& | 与 | a&b | 两个位都为1时,结果才为1。 |
| | 或 | a|b | 两个位都为0时,结果才为0。 |
~ | 取反 | ~a | 0变1,1变0。 |
^ | 异或 | a^b | 两个位相同为0,相异为1。 |
<< | 左移位 | a<<2 | 各二进位全部左移若干位,高位丢弃,低位补0。 |
>> | 右移位 | b>>4 | 各二进位全部右移若干位,对无符号数,高位补0,有符号数,右移补1 |
>>> | 无符号右移位 | x>>>2 | 各二进制全部右移若干位,高位补0,低位丢弃 |
A | B | A&B | A|B | A^B |
---|---|---|---|---|
true | true | true | true | false |
true | false | false | true | true |
false | true | false | true | true |
false | false | false | false | false |
A | B | A&B | A|B | A^B | ~A |
---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 1 |
1 | 0 | 0 | 1 | 1 | 0 |
0 | 1 | 0 | 1 | 1 | 1 |
1 | 1 | 1 | 1 | 0 | 0 |
操作 | 结果 | 等同于 | 结果 |
---|---|---|---|
5&1 | 1 | 0101 & 0001 | 0001 |
5 | 1 | 5 | 0101 | 0001 | 0101 |
5^1 | 4 | 0101 ^ 0001 | 0100 |
~5 | 10 | ~0101 | 1010 |
5<<1 | 10 | 0101<<1 | 1010 |
5>>1 | 2 | 0101>>1 | 0010 |
5>>>1 | 2 | 0101>>>1 | 0010 |
# 例子1
A = 10001001
B = 10010000
A | B = 10011001
# 例子2
A = 10001001
C = 10001000
A | C = 10001001
# 例子1
A = 10001001
B = 10010000
A & B = 10000000
# 例子2
A = 10001001
C = 10001000
A & C = 10001000
比如我们一般写代码的话,判断数组里边是否存在某一个值,是这样判断的
let arr = [4,7,10]
if (arr.indexOf(7) > -1) {
console.log('测试')
}//测试
其实我们可以写的更简洁一点:
let arr = [4,7,10]
if (~arr.indexOf(7)) {
console.log('测试')
}
console.log(1 << 5) // 32, 即 2的5次方
console.log(1 << 10) // 1024, 即 2的10次方
// 但是要注意使用
console.log(a = 2e10) // 20000000000
console.log(a << 2) // -1604378624
//if 判断
let show;
if (show) {
show = 0;
} else {
show = 1;
}
console.log(show) // 1
//三目运算符
show = show ? 0 : 1;
console.log(show) // 1
//可以换成^
show ^= 1;
console.log(show) //1
有符号左移会将32位二进制数的所有位向左移动指定位数。如:
var num = 2; // 二进制10
num = num << 6; // 二进制1000000,十进制128
console.log(num)
//另外
function ceshi(n) {
return 1 << n;
}
console.log(ceshi(5))//32
有符号右移会将32位二进制数的所有位向右移动指定位数。如:
var num = 64; // 二进制1000000
num = num >> 7; // 二进制10,十进制0
console.log(num)
//另外
var num = 64 >> 1; // 32
console.log(num)
正数的无符号右移与有符号右移结果是一样的。负数的无符号右移会把符号位也一起移动,而且无符号右移会把负数的二进制码当成正数的二进制码:
var num = -64; // 11111111111111111111111111000000
num = num >>> 7; // 33554431
console.log(num)
//另外,我们可以利用无符号右移来判断一个数的正负:
function ceshi(n) {
return (n === (n >>> 0)) ? true : false;
}
console.log(ceshi(-1)) // false
console.log(ceshi(1)) // true
|与||操作符的道理也是一样的,只要两个数中有一个数为1,结果就为1,其他则为0。
var num = Math.floor(1.3); // 1
console.log(num)//1
//另外
var num = 1.3 | 0; // 1
console.log(num)
等等 其他一些花里胡哨的操作这就不在举例了
只有a和b任意一位为1时,a|b就是1
8 | = | 1 | 0 | 0 | 0 |
---|---|---|---|---|---|
2 | = | 0 | 0 | 1 | 0 |
10 | = | 1 | 0 | 1 | 0 |
应用场景: 取整
function toInt(num) {
return num | 0
}
console.log(toInt(1.9)) // 1
console.log(toInt(1.32322)) // 1
只有 a 和 b 都为1时,a & b 就是 1
8 | = | 1 | 0 | 0 | 0 |
---|---|---|---|---|---|
2 | = | 0 | 0 | 1 | 0 |
0 | = | 0 | 0 | 0 | 0 |
应用场景:判断奇偶
var number = 0
console.log(number & 1 === 1)
当两个操作数相应的比特位有且只有一个1时,结果为1,否则为0。
8 | = | 1 | 0 | 0 | 0 |
---|---|---|---|---|---|
2 | = | 0 | 0 | 1 | 0 |
10 | = | 1 | 0 | 1 | 0 |
应用场景: 交换两个变量的值
let a = 5,
b = 6;
console.log(a = a ^ b);//3
console.log(b = a ^ b);//5
console.log(a = a ^ b);//6
// 还可以通过运算
console.log(a = a + b);//11
console.log(b = a - b);//6
console.log(a = a - b);//5
// es 6
console.log([a, b] = [b, a])//[ 6, 5 ]
// 原理剖析:a = a ^ b; b = a ^ b 相当与 b = a ^ b ^ b = a ^ (b ^ b) = a ^ 0 = a;
简单的说就是对一个二进制数进行取反,即0变成1,1变成零(零变一,一变零)
应用场景:小数取整,因为位运算的操作值要求是整数,其结果也是整数,所以经过位运算的都会自动变成整数
当有两个整数 a 和 b ,在通常情况下我们有“+”运算符对其进行相加运算:
let sum = a + b;
但是 JS 的Number类型是有内存限制的,数字的长度超过8字节,便会损失精度。
JS 中整数的最大安全范围可以查到是:9007199254740991
为了避免出现精度损失,我们可以采用另一种方法来巧妙地运算
使用字符串进行计算
将每一个字符当做一位来单独进行计算,将其分出来非进位和与进位和,将两者进行一个字符串的拼接
let a = "9007199254740991";
let b = "1234567899999999999";
function add(a ,b){
//取两个数字的最大长度
let maxLength = Math.max(a.length, b.length);
//用0去补齐长度
a = a.padStart(maxLength , 0);//"0009007199254740991"
b = b.padStart(maxLength , 0);//"1234567899999999999"
//定义加法过程中需要用到的变量
let t = 0;
let f = 0; //"进位"
let sum = "";
for(let i=maxLength-1 ; i>=0 ; i--){
t = parseInt(a[i]) + parseInt(b[i]) + f; //每一位字符的number和
f = Math.floor(t/10); //将和进行一个进位(类似<<)处理,并取出”进位“,这个进位要在下一位运算中加上
sum = t%10 + sum; //将第i位的字符结果拼接到sum上
}
if(f == 1){
sum = "1" + sum;
}
return sum;
}
JS中最大数支持64bit,但是注意运算起来,如果是两个数,比如位操作,最大支持31bit(2147483647),如果超出之后,比如
2147483647 & 2147483647 结果与之后是32位,这个不支持已经溢出,所以安全的位操作的最大位数30位
2147483647 & 2147483647 // 2147483647
4294967295 & 4294967295 // -1
没有什么必要在 js 中使用位运算,未必就能提高性能,引擎未必不会帮我们优化,由于 number 类型存储的方式,就可能会踩坑。
这套运算符针对的是整数,所以对 Javascript 完全无用,因为 Javascript 内部,所有数字都保存为双精度浮点数。如果使用它们的话,Javascript 不得不将运算数先转为整数,然后再进行运算,这样就降低了速度。而且"按位与运算符"&同"逻辑与运算符"&&,很容易混淆。
以上的例子在平常可能会比较容易用到或看到,也是属于比较容易理解的。一些比较复杂的、难理解的,对于我来说,觉得应该尽量少用,因为会给自己带来麻烦。但是巧妙的使用位运算可以大量减少运行开销,优化算法。我们都知道计算机存储的都是二进制数据,这是由计算机本身的设计决定的。
https://www.w3school.com.cn/js/js_bitwise.asp
https://blog.csdn.net/deaidai/article/details/78167367
https://blog.csdn.net/weixin_43808903/article/details/110262130