源码、反码和补码
很多人都记不清源码、反码和补码的区分,都是二进制,其实记忆起来很简单,分为正数和负数来记。正数的原码、反码和补码都是一样的,负数的原码符号位为1,反码是在原码的基础上进行改变:保持符号位不变,其他位取反;补码是在反码的基础上:反码的末位加1。如下图所示:
位运算规则
与:&
。运算规则:对于每个二进制位,都为1,结果才为1,否则为0。
0 & 0 = 0
0 & 1 = 0
1 & 0 = 0
1 & 1 = 1
或:|
。运算规则:对于每个二进制位,只要有一个为1,结果就为1。
0 ∣ 0 = 0
0 ∣ 1 = 1
1 ∣ 0 = 1
1 ∣ 1 = 1
异或:⊕
,代码中用^
表示。运算规则:对于每个二进制位,相同为0,不相同为1。
0 ⊕ 0 = 0
0 ⊕ 1 = 1
1 ⊕ 0 = 1
1 ⊕ 1 = 0
取反:~
,运算规则:对于每个二进制位进行取反操作,1变为0,0变为1。
~1 = 0
~0 = 1
移位运算与乘除法
左移运算:<<
,将全部二进制位左移若干位,高位丢弃,低位补0。对于左移运算,算术移位和逻辑移位是相同的。
右移运算:>>
,将全部二进制位向右移动若干位,低位丢弃,高位的补位由算术移位或逻辑移位决定:
- 算数右移,最高位补最高位,如-50(补码:1100 1110)算数右移两位是-13,对应二进制:1111 0011(补码)
- 逻辑右移,最高位补0,如-50逻辑右移两位是51,对应的二进制表示:0011 0011(补码)
将一个数左移 k位,等价于将这个数乘以 2 k 2^k 2k。当乘数不是 2 的整数次幂时,可以将乘数拆成若干项 2 的整数次幂之和,例如, a × 6 a×6 a×6等价于 ( a < < 2 ) + ( a < < 1 ) (a<<2)+(a<<1) (a<<2)+(a<<1)。算术右移运算对应除法运算,将一个数右移 k 位,相当于将这个数除以 2 k 2^k 2k,结果向下取整。
位运算常用技巧
-
幂等律: a & a = a a \& a=a a&a=a, a ∣ a = a a∣a = a a∣a=a(注意异或不满足幂等律);
-
交换律: a & b = b & a a \& b = b \& a a&b=b&a, a ∣ b = b ∣ a a ∣ b = b ∣ a a∣b=b∣a, a ⊕ b = b ⊕ a a ⊕ b = b ⊕ a a⊕b=b⊕a;
-
结合律: ( a & b ) & c = a & ( b & c ) (a \& b) \& c = a \& (b \& c) (a&b)&c=a&(b&c),
( a ∣ b ) ∣ c = a ∣ ( b ∣ c ) (a ∣ b) ∣ c = a ∣ (b ∣ c) (a∣b)∣c=a∣(b∣c),
( a ⊕ b ) ⊕ c = a ⊕ ( b ⊕ c ) (a ⊕ b) ⊕ c = a ⊕ (b ⊕ c) (a⊕b)⊕c=a⊕(b⊕c);
-
分配律: ( a & b ) ∣ c = ( a ∣ c ) & ( b ∣ c ) (a \& b) ∣ c = (a ∣ c) \& (b ∣ c) (a&b)∣c=(a∣c)&(b∣c),
( a ∣ b ) & c = ( a & c ) ∣ ( b & c ) (a ∣ b) \& c = (a \& c) ∣ (b \& c) (a∣b)&c=(a&c)∣(b&c),
( a ⊕ b ) & c = ( a & c ) ⊕ ( b & c ) (a ⊕ b) \& c = (a \& c) ⊕ (b \& c) (a⊕b)&c=(a&c)⊕(b&c);
-
德摩根律: ∼ ( a & b ) = ( ∼ a ) ∣ ( ∼ b ) , ∼ ( a ∣ b ) = ( ∼ a ) & ( ∼ b ) ∼(a \& b) = (∼a) ∣ (∼b),∼(a ∣ b) = (∼a) \& (∼b) ∼(a&b)=(∼a)∣(∼b),∼(a∣b)=(∼a)&(∼b);
-
取反运算性质: − 1 = ∼ 0 , − a = ∼ ( a − 1 ) −1 = ∼0,−a = ∼(a−1) −1=∼0,−a=∼(a−1);
-
与运算性质: a & 0 = 0 a \& 0 = 0 a&0=0, a & ( − 1 ) = a a \& (−1) = a a&(−1)=a, a & ( ∼ a ) = 0 a \& (∼a) = 0 a&(∼a)=0;
-
或运算性质: a ∣ 0 = a a ∣ 0 = a a∣0=a;
-
异或运算性质: a ⊕ 0 = a a ⊕ 0 = a a⊕0=a, a ⊕ a = 0 a ⊕ a=0 a⊕a=0;
如何获取、设置和更新某个为位的数据,也有固定的套路。
-
获取
将1左移
i
位,得到形如0001 0000
的值。接着对这个值与num
执行&
操作,从而将i
位之外的所有位清零,最后检查结果是否为0。不为0说明i
位为1,否则i
位为0。function getBit(num, i) { return ((num & (1<<i)) !== 0); }
-
设置(将某一位设置为1)
先将1左移
i
位,得到形如0001 0000
的值,接着对这个值和num
执行|
操作,这样只会改变i
位的数据。这样做则会使除i
位外的位均为0,故不会影响num
的其余位。function setBit(num, i) { return (num | (1<<i)); }
-
清零(将某一位设置为0)
该方法与
setBit
相反,首先将1左移i
位,得到形如0001 0000
的值,接着对这个值取反进而得到类似1110 1111
的值,接着对该值执行&
操作,故而不会影响到num
的其余位,只会清零i
位。function clearBit(num, i) { let mask = ~(1<<i) return num & mask; }
-
更新
该方法将
setBit
和clearBit
结合,首先用如1110 1111
的值将num
的第i
位清零。接着将待写入值v
左移i
位,得到一个i
位为v
但其余位都为0的数。最后对之前的结果执行|
操作,v
为1则num
的i
位更新为1,否则为0:function updateBit(num, i, v) { let mask = ~(1<<i); return (num & mask) | (v<<i); }