js二进制和位运算

js 二进制与位运算

生活中,我们经常用到的是以十进制为单位,进位是满 10 进 1,而计算机是用二进制,那么就是满 2 进 1 喽,为什么采用二进制呢?简单说下,Ta的由来其实是根据电路的开关闭合,电路只有 0 和 1,具体的详情这里不多做解释了,可以自行百科^_^。javascript 采用有符号 32 位的 2 进制,可表示 4294967295 个整数(含正负),范围是 -2147483648(2 的 32 次方) ~ 2147483647。

带符号二进制基本规则与几个概念

在计算机中规定最高位是符号位,0 为正,1 为负。正数表示是原码,负数是原码的补码。

原码

除去符号位,其他 32 位都为正。

例:00000000000000000000000000001010 原码 00000000000000000000000000001010。

反码

所谓反码,除符号位外,其他位 0 变 1,1 变 0。 例:1010 反码 1101

补码

所谓补码,就像是小学学过的 10 进制补数,举个例子就很好理解,3 的补数是多少?是 7。4 的补数是 6,补数就是 10 减去这个数。在二进制的补码就是相加等于 0,互为相反数。

  • 0 的补码 0 的原码、反码、补码均为 0
  • 正数的补码 原码
  • 负数的补码 原码除符号位外的所有位取反+1。如何推出来的,看了一篇文章,挺有意思。点击查看

位运算

按位与(AND) &

描述:a & b 对于每一个比特位,只有两个操作数相应的比特位都是 1 时,结果才为 1,否则为 0。(摘自 MDN)

举例:2 & 3 = 2 0000 0010 & 0000 0011 ——————— 0000 0010

应用场景:

判断奇偶

1
console.log(num & 1) // 1 是奇数  0是偶数

权限设置:解权限

因为奇数的最后一位永远是 1。 举例:num 是 3 0000 0011 0000 0001 ————— 0000 0001

按位或(OR)|

描述:对于每一个比特位,当两个操作数相应的比特位至少有一个 1 时,结果为 1,否则为 0。(摘自 MDN)

举例:2 | 3 = 3 0000 0010 | 0000 0011 ——————— 0000 0011

应用场景:

小数取整

1
2
3
4
function fn(n) {
return n | 0
}
fn(3.2) // 3

权限

假设我们读的权限是 1 写的权限是 2,如果一个用户同时拥有读和写两种权限,正常来分配就是用一个数组[1,2],那么用或运算符如何分配呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Permission {
constructor() {
this.instance = null
this.permissions = {
r: 1, // 读
w: 2, // 写
e: 4 // 执行
}
}
getR() { return this.permissions.r }
getW() { return this.permissions.w }
getE() { return this.permissions.e }

getRW() { return this.permissions.r | this.permissions.w }
getRE() { return this.permissions.r | this.permissions.e }
getWE() { return this.permissions.w | this.permissions.e }

getRWE() { return this.permissions.r | this.permissions.w | this.permissions.e }
getAll() { return Object.values(this.permissions).reduce((p1, p2, i) =p1 | p2) }

isR(per) { return Boolean(per & this.permissions.r) }
isW(per) { return Boolean(per & this.permissions.w) }
isE(per) { return Boolean(per & this.permissions.e) }

static getInstance() {
!this.instance && (this.instance = new Permission())
return this.instance
}
}
const per = Permission.getInstance()
per.getRW() // 读写权限:3
per.getAll() // 所有权限:7
per.isR(3) // true 是否有读的权限
per.isW(5) // false 是否有写的权限
per.isE(5) // true 是否有执行的权限

注:基本运算过程 1 | 2 => 0000 0001 | 0000 0010 ———————————— 0000 0011 =3

按位异或(XOR)^

描述:对于每一个比特位,当两个操作数相应的比特位有且只有一个 1 时,结果为 1,否则为 0。(摘自 MDN)

举例:2 ^ 3 0000 0010 ^ 0000 0011 —————— 0000 0001

应用场景

变量交换 我们会用到两个变量的值进行交换,比如冒泡排序的两两对比,若前者比后者大就交换位置:

1
2
3
4
5
var a = 1,
b = 2
var temp = a
var a = b
var b = temp

这里是声明第三个变量来实现交换。那么,用按位异或可以这么做:

1
2
3
4
5
var a = 1,
b = 2
a ^= b
b ^= a
a ^= b

分解一下上面的: a += 1 和 a ^= b 表达式规则是一样的 所以 a ^= b =a = a ^ b, 看下面运算过程: 0000 0001 ^ 0000 0010 —————— 0000 0011 此时,a 就变成了 3。 b ^= a =b = b ^ a 0000 0010 ^ 0000 0011 —————— 0000 0001 此时,b 变成了 1。 重复第一步: 0000 0011 ^ 0000 0001 —————— 0000 0010 此时,a 变成了 2。最终 a=2,b=1 实现了变量的交换。好处是少声明了一个临时变量,效率上,亲测结果 异或运算交换并不比第三变量的效率高(测试交换 100 次)。

按位非(NOT)~

描述:反转操作数的比特位,即 0 变成 1,1 变成 0。 看到这个可能会想到上面所说的反码,这两者是有区别的,反码是按照有符号和无符号算的,取反是不管有没有符号全部 0 变 1,1 变 0 的

应用场景

小数取整 还有一个特征是取反是不能用于浮点型,所以可以用来向下取整,效果和 Math.floor()一样,~~效率要高。

1
2
3
4
function fn(n) {
return ~~n
}
fn(3.2) // 3

左移(Left shift)<<

描述:a << b ,将 a 的二进制形式向左移 b (< 32) 比特位,右边用 0 填充。(摘自 MDN) 举例:3 << 4 0000 0011 向左移动 4 位 =0011 0000 可知,左移数是越来越大的。移动运算符运算效率较高

应用场景

x 乘以 y 的 n 次方:x << y

1
1 << 3 =0000 1000 化成10进制 =1 * 23次方 =8

2 进制化成 10 进制 就是 2 的 n 次方 乘以 当前位。

有符号右移 a >b

描述:将 a 的二进制表示向右移 b (< 32) 位,丢弃被移出的位。 该操作符会将第一个操作数向右移动指定的位数。向右被移出的位被丢弃,拷贝最左侧的位以填充左侧。由于新的最左侧的位总是和以前相同,符号位没有被改变。所以被称作“符号传播”。 举例: 正数右移:4 >2

1
2
3
4
   0000 0000 0000 0000 0000 0000 0000 0100
> 2
——————————————————————————————————————————
0000 0000 0000 0000 0000 0000 0000 0001

正数右移,不够的位数用符号位 0 补全,可以发现规律,结果是 4 / (2 的 2 次方) =当前位/(2 的移位的位数的次方)

负数右移:-3 >4

1
2
3
4
5
6
   1000 0000 0000 0000 0000 0000 0000 0011 的补码是=>
1111 1111 1111 1111 1111 1111 1111 1101 向右移动4位,右边舍去,左侧用符号位补齐
> 4
——————————————————————————————————————————
1111 1111 1111 1111 1111 1111 1111 1111 补码是=>
1000 0000 0000 0000 0000 0000 0000 0001 所以,最终是-1

无符号右移 a >>b

描述:将 a 的二进制表示向右移 b (< 32) 位,丢弃被移出的位,并使用 0 在左侧填充。 正数右移:4 >>2

1
2
3
4
    0000 0000 0000 0000 0000 0000 0000 0100
>> 2
———————————————————————————————————————————
0000 0000 0000 0000 0000 0000 0000 0001

可以发现 4 >>2 和 4 >2 结果都是 1,也就是说,对于非负数来说,有无符号右移结果都是相同。 负数右移:-4 >>2

1
2
3
4
5
6
    1000 0000 0000 0000 0000 0000 0000 0100 补码=>
1111 1111 1111 1111 1111 1111 1111 1100
>> 2
———————————————————————————————————————————
0011 1111 1111 1111 1111 1111 1111 1111
化成10进制=2^29...2^1+1 = 1073741823

总结:

位运算 优势:乘除,取模,运算效率高 劣势:可读性较低,对初学者有难度,工作中应当按需所取,不应为炫技滥用。 好多源码都有用到位运算(express、koa、koa-body),现在学习了位运算,妈妈再也不担心读不懂源码了!

本文结束感谢您的阅读

本文标题:js二进制和位运算

文章作者:陈宇(cosyer)

发布时间:2020年05月06日 - 14:05

最后更新:2020年05月07日 - 23:05

原始链接:http://mydearest.cn/2020/js%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%92%8C%E4%BD%8D%E8%BF%90%E7%AE%97.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

坚持原创技术分享,您的支持将鼓励我继续创作!