位运算的一些应用

大部分内容选自百度百科(搜索位运算)

1. 判断奇偶

奇数 & 1 == 1

偶数 & 1 == 0

func main() {
    fmt.Println(isOdd(3))
    fmt.Println(isOdd(-3))
    fmt.Println(isOdd(0))
    fmt.Println(isOdd(4))
}

func isOdd(v int) bool {
    return v&1 == 1
}

结果:

true
true
false
false

1.1 判断二进制1的个数的奇偶性

    v := 13
    v ^= v >> 1
    v ^= v >> 2
    v ^= v >> 4
    v ^= v >> 8
    v ^= v >> 16
    v &= 1

以4位表示,这个只需要运行到右移2位即可:

1011 ^ (1011 >> 1) == 1110

结果的右起第1位0是原数中右起第1和第2位异或的结果(1 ^ 1),它表示后两位有奇数个1还是偶数个1.

结果的右起第2位是原数中右起第2位和第3位异或的结果,但是第2位已经统计过了,所以,这一位不考虑。

结果的右起第3位是原数中右起第3位和第4位异或的结果,1表示有奇数个1。

结果的右起第4为不考虑了。

1110 ^ (1110 >> 2) == 1101

1110中有用的位数是右起第1位和第3位,所以只考虑这两位,右移2位将第1和第3位对齐,做异或,得到的结果就是1个数的奇偶性,其余3位丢弃,所以最后使用&1的方式取最后一位。

这是分治思想的一种应用。

2. 改变二进制某些特定位的值

  • 把最后一位变为1
    1010 | 1 == 1011

  • 把最后一位变为0
    0101 | 1 - 1 == 0100

  • 最后一位取反
    0101 ^ 1 == 0100

  • 把右数第k位变成1
    (k=2) 0101 ^ (1 << k-1) == 0111

  • 把右数第k位变成0
    (k=3) 0101 & (~(1 << k-1)) == 0001

  • 右数第k位取反
    (k=2) 0101 ^ (1 << k-1) == 0111

  • 把右边连续的1变成0
    10111 & (10111 + 1) == 10000

  • 把右起第一个0变成1
    10111 | (10111 + 1) == 11111

  • 把右边连续的0变成1
    1000 ^ (1000 - 1) == 1111

  • 取右边连续的1
    (10111 ^ (10111 + 1)) >> 1 == 00111

  • 去掉右起第一个1的左边(除了右起第一个1,其余位置0)
    11100 & (11100 ^ (11100 - 1)) == 00100
    或者
    11100 & (-11100) == 00100

3. 位运算的黑魔法

本节选自 https://graphics.stanford.edu/~seander/bithacks.html
测试代码部分使用golang

3.1 检测两个整数符号是否相同

int x, y;               // input values to compare signs
bool f = ((x ^ y) < 0); // true iff x and y have opposite signs

测试代码:

    xarr := []int{-3, 0, 4}
    yarr := []int{-3, 0, 4}
    for _, x := range xarr {
        for _, y := range yarr {
            f := (x ^ y) < 0
            if f {
                fmt.Printf("%d and %d have opposite signs.\n", x, y)
            } else {
                fmt.Printf("%d and %d don't have opposite signs.\n", x, y)
            }
        }
    }

结果:

-3 and -3 don't have opposite signs.
-3 and 0 have opposite signs.
-3 and 4 have opposite signs.
0 and -3 have opposite signs.
0 and 0 don't have opposite signs.
0 and 4 don't have opposite signs.
4 and -3 have opposite signs.
4 and 0 don't have opposite signs.
4 and 4 don't have opposite signs.

3.2 判断一个数是不是2的倍数

f = (v & (v - 1)) == 0

测试代码:

    nums := []int{-5, -2, 0, 4, 5}
    for _, num := range nums {
        f := (num & (num - 1)) == 0
        fmt.Printf("%d is a power of 2? %t \n", num, f)
    }

结果:

-5 is a power of 2? false 
-2 is a power of 2? false 
0 is a power of 2? true 
4 is a power of 2? true 
5 is a power of 2? false 
  • 对负数无效
  • 如果不希望把0算进去,可以用 f = v && !(v & (v - 1))

4. 两个数相加或相减是否溢出

两数相加溢出:

func CheckedAddInt64(a, b int64) (sum int64, err error) {
    sum = a + b
    // ^的结果>=0说明是同符号,<0说明不同符号
    // 两个同符号的数相加出现不同符号的结果,说明溢出了
    if ((a ^ b) >= 0) && ((a ^ sum) < 0) {
        err = ErrOverflow
    }
    return
}

两数相减溢出:

func CheckedSubtractInt64(a, b int64) (sub int64, err error) {
    sub = a - b
    // a与b不同符号,相当于两个同符号的数相加,但结果与a不同,说明溢出
    if ((a ^ b) < 0) && ((a ^ sub) < 0) {
        err = ErrOverflow
    }
    return
}

你可能感兴趣的:(编程基础)