简介
位运算(bitwise operation)其实就是将数转换为二进制逐位运算。
通常在处理器上,位运算的效率比乘除法高。
位运算
非(NOT)
非运算会对一个二进制数的每一位执行逻辑非运算,即取反,将$0$变成$1$,将$1$变成$0$。
在大多数编程语言中,其符号为~
。
非运算的结果是值的二进制补码减一,即$\mathrm{NOT} \ x = -x - 1$。
与(AND)
与运算对两个相同长度的二进制数每一组对应的bit进行比较并执行逻辑与运算,相当于将它们相乘。若两个bit都为$1$,则结果为$1$($1 \times 1 = 1$);否则结果为$0$($1 \times 0 = 0, 0 \times 0 = 0$)。
在大多数编程语言中,其符号为&
。
与运算可以用于确认某一位是$1$还是$0$。
例如,对于给定的数$0011 \ (= (3)_{10})$,我们要确认第$2$位是否是$1$,我们可以将其和$0010$(只有第$2$位是$1$)进行与运算:
由于结果$0010$不为$0$,所以我们知道原数的第$2$位是$1$。这被称作位掩码(bit mask)。
与运算可以将某一位设置为$0$。例如,我们要把$0110 \ (= (6)_{10})$的第$3$位设置为$0$,可以将其和$1011$(只有第$3$位是$0$)进行与运算:
由于这个属性,二进制数的奇偶校验就很方便了:
因为$6 \ \mathrm{AND} \ 1 = 0$,所以$6$可以被$2$整除,因此$6$是偶数。
或(OR)
或运算对两个相同长度的二进制数每一组对应的bit进行比较并执行逻辑或运算。若两个bit都为$0$,则结果为$0$;否则结果为$1$。
在大多数编程语言中,其符号为|
。
或运算可以将某一位设置为$1$。例如,我们要把$0110 \ (= (6)_{10})$的第$4$位设置为$1$,可以将其与$1000$(只有第$4$位是$1$)进行或运算:
异或(XOR)
异或运算对两个相同长度的二进制数每一组对应的bit进行比较并执行逻辑异或运算。若只有其中一个bit为$1$,则结果为$1$;否则结果为$0$。即两者状态不同,结果为$1$;两者状态相同,结果为$0$。
在大多数编程语言中,其符号为^
。数学中还可以用符号$\oplus$表示异或。
异或运算可以将某一位反转。例如,我们要把$0110 \ (= (6)_{10})$的第$1$位和第$3$位反转,可以将其和$0101$(第$1$位和第$3$位是$1$)进行异或运算:
通过定义可以得到,一个数异或自己总是得$0$。因此,将一个变量归零可以使用异或自己的方式,这种方式的效率通常比直接赋值为$0$要高。
数学等价
对于非负整数$x$和$y$,$x \ge y$,位运算可以写成如下形式:
移位
移位会将二进制数字整体向左或向右移动,任意一端移出的位会被丢弃。
在左移中,$0$会从右侧移入;而在右移中,符号位会从左侧移入。
左移$n$位等同于乘以$2^{n}$,而右移$n$位等同于除以$2^{n}$并向负无穷取整。
在大多数编程语言中,左移为<<
,右移为>>
。
应用
乘以$2^{m}$
1
2
3int mulTwoPower(int n, int m=1) {
return n << m;
}除以$2^{m}$
1
2
3int divTwoPower(int n, int m=1) {
return n >> m;
}判断奇偶性
1
2
3bool isOdd(int n) {
return n & 1;
}取两个数的最大值
1
2
3
4int max(int a, int b) {
// 若n为非负数,n >> 31的值为0,否则为-1
return (b & ((a - b) >> 31)) | (a & (~(a - b) >> 31));
}取两个数的最小值
1
2
3
4int min(int a, int b) {
// 若n为非负数,n >> 31的值为0,否则为-1
return (a & ((a - b) >> 31)) | (b & (~(a - b) >> 31));
}取绝对值
1
2
3
4int iabs(int n) {
// 若n为非负数,n >> 31的值为0,否则为-1
return (n ^ (n >> 31)) - (n >> 31);
}判断是否是$2$的幂
1
2
3bool isTwoPower(int n) {
return n > 0 && (n & (n - 1)) == 0;
}对$2^{n}$取余
1
2
3int bmodTwoPower(int n, int m) {
return m & (n - 1);
}判断符号是否相同
1
2
3bool isSameSign(int a, int b) {
return (a ^ b) >= 0;
}加法
1
2
3
4
5
6
7
8
9
10
11int add(int a, int b) {
int sum = 0, carry = 0;
do {
sum = a ^ b;
carry = (a & b) << 1;
a = sum;
b = carry;
}
while (carry);
return sum;
}