-
Notifications
You must be signed in to change notification settings - Fork 137
Description
js 二进制与位运算
生活中,我们经常用到的是以十进制为单位,进位是满 10 进 1,而计算机是用二进制,那么就是满 2 进 1 喽,为什么采用二进制呢?简单说下,他的由来其实是根据电路的开关闭合,电路呢只有 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
应用场景:
判断奇偶
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
应用场景:
小数取整
function fn(n) {
return n | 0
}
fn(3.2) // 3权限
假设我们读的权限是 1 写的权限是 2,如果一个用户同时拥有读和写两种权限,正常来分配就是用一个数组[1,2],那么用或运算符如何分配呢?
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
应用场景
变量交换
我们会用到两个变量的值进行交换,比如冒泡排序的两两对比,若前者比后者大就交换位置:
var a = 1,
b = 2
var temp = a
var a = b
var b = temp这里是声明第三个变量来实现交换。那么,用按位异或可以这么做:
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()一样,~~效率要高。
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 << 3 => 0000 1000 化成10进制 => 1 * 2的3次方 => 82 进制化成 10 进制 就是 2 的 n 次方 乘以 当前位。
有符号右移 a >> b
描述:将 a 的二进制表示向右移 b (< 32) 位,丢弃被移出的位。
该操作符会将第一个操作数向右移动指定的位数。向右被移出的位被丢弃,拷贝最左侧的位以填充左侧。由于新的最左侧的位总是和以前相同,符号位没有被改变。所以被称作“符号传播”。
举例:
正数右移:4 >> 2
0000 0000 0000 0000 0000 0000 0000 0100
>> 2
——————————————————————————————————————————
0000 0000 0000 0000 0000 0000 0000 0001正数右移,不够的位数用符号位 0 补全,可以发现规律,结果是 4 / (2 的 2 次方) => 当前位/(2 的移位的位数的次方)
负数右移:-3 >> 4
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
0000 0000 0000 0000 0000 0000 0000 0100
>>> 2
———————————————————————————————————————————
0000 0000 0000 0000 0000 0000 0000 0001可以发现 4 >>> 2 和 4 >> 2 结果都是 1,也就是说,对于非负数来说,有无符号右移结果都是相同。
负数右移:-4 >>> 2
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),现在学习了位运算,妈妈再也不担心读不懂源码了!