发布于 

位运算的几个常用例子

众所周知,在代码中使用位运算能大幅提高运算效率。现代计算机由于性能提升明显、一些编译器也会对此做优化。但是对于一些功能要求高效率的时候,尤其是底层常用的函数体,位运算是一个比较好的选择。例如: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,求二进制码。

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

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

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

  1. 乘除 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
  1. 取整数最大值

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

1
2
const MaxUint = ^uint(0)
fmt.Println(MaxUint)

4. 加密、解密
待补

[参考资料]

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