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 | function fn(n) { |
权限
假设我们读的权限是 1 写的权限是 2,如果一个用户同时拥有读和写两种权限,正常来分配就是用一个数组[1,2],那么用或运算符如何分配呢?
1 | class Permission { |
注:基本运算过程 1 | 2 => 0000 0001 | 0000 0010 ———————————— 0000 0011 =3
按位异或(XOR)^
描述:对于每一个比特位,当两个操作数相应的比特位有且只有一个 1 时,结果为 1,否则为 0。(摘自 MDN)
举例:2 ^ 3 0000 0010 ^ 0000 0011 —————— 0000 0001
应用场景
变量交换 我们会用到两个变量的值进行交换,比如冒泡排序的两两对比,若前者比后者大就交换位置:
1 | var a = 1, |
这里是声明第三个变量来实现交换。那么,用按位异或可以这么做:
1 | var a = 1, |
分解一下上面的: 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 | function fn(n) { |
左移(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 * 2的3次方 =8 |
2 进制化成 10 进制 就是 2 的 n 次方 乘以 当前位。
有符号右移 a >b
描述:将 a 的二进制表示向右移 b (< 32) 位,丢弃被移出的位。 该操作符会将第一个操作数向右移动指定的位数。向右被移出的位被丢弃,拷贝最左侧的位以填充左侧。由于新的最左侧的位总是和以前相同,符号位没有被改变。所以被称作“符号传播”。 举例: 正数右移:4 >2
1 | 0000 0000 0000 0000 0000 0000 0000 0100 |
正数右移,不够的位数用符号位 0 补全,可以发现规律,结果是 4 / (2 的 2 次方) =当前位/(2 的移位的位数的次方)
负数右移:-3 >4
1 | 1000 0000 0000 0000 0000 0000 0000 0011 的补码是=> |
无符号右移 a >>b
描述:将 a 的二进制表示向右移 b (< 32) 位,丢弃被移出的位,并使用 0 在左侧填充。 正数右移:4 >>2
1 | 0000 0000 0000 0000 0000 0000 0000 0100 |
可以发现 4 >>2 和 4 >2 结果都是 1,也就是说,对于非负数来说,有无符号右移结果都是相同。 负数右移:-4 >>2
1 | 1000 0000 0000 0000 0000 0000 0000 0100 补码=> |
总结:
位运算 优势:乘除,取模,运算效率高 劣势:可读性较低,对初学者有难度,工作中应当按需所取,不应为炫技滥用。 好多源码都有用到位运算(express、koa、koa-body),现在学习了位运算,妈妈再也不担心读不懂源码了!