位运算的妙用

位运算的妙用

  • 本文代码在 python 3.5 下测试可行

预警

  • 本文完全是一些奇技淫巧, 实用性较低.

目录

  • 内容包括
    • 求整型数字的二进制形式中 1 的个数
    • 不使用加减乘除实现加法

求整型数组的二进制形式中 1 的个数

基础方法

- 假设我们现在有一个整型数字 a , 那么我们想要计算其中拥有1 的位数的数量, 那么最基础的想法就是将其看做一个二进制串
- 假设我们需要计算的32位整型数字是 26 , 那么其二进制串为 11010, 这里我们省略了前导的27个 0.
- 我们可以依次将当前二进制串的每一个与 1 按位与, 如果是结果为1, 那么计数就加 1.

def count_1s_basically(x):
    count = 0
    while x > 0:
        if x & 1:
            count += 1
        x >>= 1
    return count

去除连续0

  • 对于上面的算法, 其时间复杂度为 O(logk) , 其中 k 是数字的位数, 那么有没有更快的算法呢?
  • 以 12 的二进制串 – 1100 为例, 我们可以发现连续0对我们的计数毫无作用, 那么我们如何去除原数中存在连续的 0 呢?
  • 如果我们12 减去 1 并且得到 1011 (即11), 那么对这两个数进行位与, 我们就可以可到1000 (即8), 在消除连续0的时候, 我们也消去了一个1.
  • 显然, 这个方法是可以进行推广的. 那么, 只要我们不断地进行迭代, 直到最后这个数为0. 显然由于去除了连续 0, 这个方法比基础方法更好.
def count_1s_enhanced(x):
    count = 0
    while x > 0:
        count += 1
        x = x & (x - 1)
    return count

并行方法

  • 但是这种方法在我们碰到几乎没有什么连续 0 的情况下, 效率就会退化为基础方法的 O(k)
  • 对于接触过操作系统的人, 一定了解并行的概念. 那么我们是否可以将并行的概念引入我们的计算中. 比如我们对于32位的整型, 将其分别切分成4块(每块8位), 对于每块都进行一次计算, 尽管这似乎有点小题大做.
  • 显然可以分别使用0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff 来实现对每一块的取出, 然后我们再使用去除连续0的算法进行计算即可.
def count_1s_simultaneously(x):
    count = 0
    operators = [0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff]
    blocks = [x & operator for operator in operators]  # 列表推导
    for block in blocks:
        count += count_1s_enhanced(block)
    return count
算法的升级
  • 虽然这能够帮助我们将原来的数字分成四块以进行分别, 但似乎并不能够加速我们的计算, 因为我们要划分它的代价并且对其进行分配的代价似乎和计算的代价差不了多少,甚至更高.
  • 接下来我们实现一个真正的并行算法, 它能够同时对所有的块进行计算而不需要将其散布到不同的线程/进程中.
  • 首先, 我们将每8个比特位限制成一个块, 然后在块内进行计数. 例如, 对于一个长度为8个比特的数, 我们可以依次让其与 1 位与, 然后就能够得到在这个块内的数字的 1 的数量.
 # 显然我们可以将这个结论推广到任意以 8 为倍数的比特块中.
 # 例如从对于16位数字, 我们就可以将数字 x 与 0x0101做位与
 val = 0 
 # x 为这个 8 比特数
 for index in range(8):
     val += x & 0x01
     x >>= 1
  • 因为每块是独立的, 那么我们完全可以将得到的计数进行叠加, 以实现真正的并行. 例如, 对于一个16位的数字来说, 此时我们已经得到了其每个大小为8的比特块对于 1 的计数.那么我们就可以对这两个数进行相加.
 # 直观上看就是将前面的 8 位数字与后面 8 位数字对齐, 然后进行加法. 显然这时候我们就得到了这个16位数字的 1 的计数
 val += val >> 8 
  • 如果你能够理解上面的式子的意义, 那么我们就可以将其推广到64 位整型数, 代码如下
def count_1s_simultaneously_enhanced(x):
    val = 0
    for index in range(8):
        val += x & 0x0101010101010101
        x >>= 1
    val += val >> 32
    val += val >> 16
    val += val >> 8
    return val & 0xff

结语

  • 如果大家有任何不懂的, 可以在文章下面进行留言.

参考

  • CSAPP
  • 编程之美
  • 也可以参考这个博客

你可能感兴趣的:(位运算的妙用)