位运算的几个常用例子

作者 Gavin 日期 2017-12-11
位运算的几个常用例子

众所周知,在代码中使用位运算能大幅提高运算效率。现代计算机由于性能提升明显、一些编译器也会对此做优化。但是对于一些功能要求高效率的时候,尤其是底层常用的函数体,位运算是一个比较好的选择。例如:golang hashmap的实现就大幅运用了位运算。接下来简单介绍几个常用的位运算。

  1. 判断奇偶(工程中做ab实验的时候会经常用到)

    1
    2
    3
    4
    5
    6
    var a,b int8
    a = 7
    b = 0
    //判断奇偶
    fmt.Println(a&1)
    fmt.Println(b&1)

    1的二进制为 0001,正数奇数最后一位为1,所以和1与 得到的为1。在讲负数的奇偶性判断前,先说下负数的二进制表示:

    对于负数x,最左边一位为符号位1,先计算出它的正数的二进制补码,然后+1 得到负数的二进制码。
    举个例子:对于int8的 -5,求二进制码。

    1. 5的二进制码为:0000 0101
    2. 求5的补码:1111 1010
    3. 补码+1,得到-5的二进制码:1111 1011

    1的二进制表示为:0000 0001,-5&1 => 0000 0001,所以奇偶性判断方法,对于负数也是适用的。

    在介绍乘除2功能前,说下算术移和逻辑移的区别。二者都是左右移动位数。但是在有无符号上有明显的不同。在无符号的情况下,二者是没有区别的,左移都是舍弃最左边的编码,最后面补0;右移都是最高位补0,舍弃最右边编码。在有符号的情况下,算术移要保持最左边的位码不变,逻辑移规则和无符号一致。
    特别说明:golang里的左右移都是算术移。

  2. 乘除 2

    • 乘以2

      1
      2
      3
      var d uint8
      d = 8
      fmt.Println(d << 1)

      对于8的二进制表示为:0000 1000,左移一位为:0001 0000,得到16。

    • 除以 2

      1
      2
      3
      4
      var d uint8
      d = 10
      fmt.Println(d >> 1)
      fmt.Println("=================")

      特别说下有符号的情况下,右移的情况: -5 >> 1
      首先,我们得到-5的二进制码:1111 1011。保持最左的符号位不变,右移一位,得到:1111 1101。
      逆向求其正数:

      • 减1,得到:1111 1100
      • 求补码:0000 0011,也就是3
        所以,得到 -5 >> 1 = -3
  3. 取整数最大值

    这是一个比较巧妙的例子,实际工程中可能用的不是太多。

    1
    2
    const MaxUint = ^uint(0)
    fmt.Println(MaxUint)
  4. 加密、解密
    待补

[参考资料]

  1. 理解有符号数和无符号数
  2. 补码