位运算

简介

位运算(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
    3
    int mulTwoPower(int n, int m=1) {
    return n << m;
    }
  • 除以$2^{m}$

    1
    2
    3
    int divTwoPower(int n, int m=1) {
    return n >> m;
    }
  • 判断奇偶性

    1
    2
    3
    bool isOdd(int n) {
    return n & 1;
    }
  • 取两个数的最大值

    1
    2
    3
    4
    int max(int a, int b) {
    // 若n为非负数,n >> 31的值为0,否则为-1
    return (b & ((a - b) >> 31)) | (a & (~(a - b) >> 31));
    }
  • 取两个数的最小值

    1
    2
    3
    4
    int min(int a, int b) {
    // 若n为非负数,n >> 31的值为0,否则为-1
    return (a & ((a - b) >> 31)) | (b & (~(a - b) >> 31));
    }
  • 取绝对值

    1
    2
    3
    4
    int iabs(int n) {
    // 若n为非负数,n >> 31的值为0,否则为-1
    return (n ^ (n >> 31)) - (n >> 31);
    }
  • 判断是否是$2$的幂

    1
    2
    3
    bool isTwoPower(int n) {
    return n > 0 && (n & (n - 1)) == 0;
    }
  • 对$2^{n}$取余

    1
    2
    3
    int bmodTwoPower(int n, int m) {
    return m & (n - 1);
    }
  • 判断符号是否相同

    1
    2
    3
    bool isSameSign(int a, int b) {
    return (a ^ b) >= 0;
    }
  • 加法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    int 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;
    }
0%