• (译)你必须知道的位运算技巧 Low Level Bit Hacks You Absolutely Must Know


    from HackerMonthly-issue15
    By Peteris Krumins

    我准备写一篇关于嵌入系统开发者所熟知的有关位运算技巧的文章. 位运算技巧可以巧妙有效的操作整数.在如计算一个整数中包含多少个1之类的操作时,这些技巧可以只用几个位操作符搞定.

    假定你已具备2的补码和位运算操作的知识.

    下面的文章将使用以下简写:

    & -  and
    | -  or
    ^ -  xor
    ~ -  not
    << -  左移
    >> -  右移

    本文里的整数指 8bit (虽然也适用于任意长度的有符号整数) 的有符号整数,我们用 x 表示,结果用 y 表示, x 的每个bit 有 b7,b6,b5,b4,b3,b2,b1 和 b0,符号位 b7 为最高位,b0为最低位.

    我将从最常用的技巧开始,然后渐渐趋于更难的技巧.并使用示例代码解释每个技巧的工作原理.

    位运算技巧1: 判断一个整数奇偶

    if ((x & 1) == 0) {
      x is even
    }
    else {
      x is odd
    }

    我相信所有人都见过这个,它的原理是判断最后一个bit b0如果是1的话就是奇数. 用1 and x 将消除除b0以外的位,那么如果结果为0 x即为偶数,否则x 为奇数.

    来看些例子,比如43,是一个奇数,用二制表示即 00101011. 它的最后一位b0 是1,将它和1 作AND 操作:

    00101011
    & 00000001
    --------
    00000001

    可以看到 AND 抹掉了b1-b7,但b0依然保持原样,运算的最终结果是1,告诉我们它是他奇数.

    那么我们再看看-43,提醒一下,在2的补码表示方法,对一个正整数取负的步骤是反转每一位然后加1,所以-43的二进制表示为11010101.再次强调一下最后一个bit是1,所以是奇数.(如果我们用1的补码就不正确了)

    再看一个偶数98,其二进制表示是 1100010

    01100010
    & 00000001
    --------
    00000000

    AND的结果是0,意味着98的b0是0,指定的整数为偶数.

    -98二进制表示为 10011110. 同样b0 为0,AND后结果为0

    位运算技巧2: 判断第n位是否置1.

    if (x & (1<<n)) {
      n-th bit is set
    }
    else {
      n-th bit is not set
    }

    在前面的技巧中,我们看到(x&1)测试第一个位是否置1.这个技巧更进一步可以判断第n位是否置1.方法是向左移n位再求AND,这会抹掉除第n位的其他位.

    下面演示了左移的效果.

    1 00000001 (等同于 1<<0)
    1<<1 00000010
    1<<2 00000100
    1<<3 00001000
    1<<4 00010000
    1<<5 00100000
    1<<6 01000000
    1<<7 10000000

    好,我们将左移n位后再和x AND,就快速抹掉x除了第n位的其他位.如果AND后结果为0,那么那个位一定是0,否则就是置1的

    看几个例子.

    122的第三位是否有值,下面这个操作会找到答案:

    122 & (1<<3)

    122的二进制表示01111010,1<<3 为 00001000.

    01111010
    & 00001000
    --------
    00001000

    可以看到最终结果不是0,所以122的第三位是置1

    那么-33的第5位是否置1呢?

    11011111 (-33 in binary)
    & 00100000 (1<<5)
    --------
    00000000

    结果是0,所以第5位是未置1的.

    技巧3 将第n位置1

    y = x | (1<<n)

    这个技巧结合了1<<n 和 OR 操作,1左移后的再和另个数OR 运算会将那个数第n位置1.因为0和任何位OR的结果都是保持不变,但是1和任何位OR结果都是1.

    假设一个数 120,我们想将它的第2位置1.

    01111000 (120 in binary)
    | 00000100 (1<<2)
    --------
    01111100

    再试试将-120第6位置1

    10001000 (-120 in binary)
    | 01000000 (1<<6)
    --------
    11001000

    技巧4 : 将第n位置0

    y = x & ~(1<<n)

    这个玩法最核心的部分是~(1<<n),它将除第n位外的位置1

    ~1 11111110 (same as ~(1<<0))
    ~(1<<1) 11111101
    ~(1<<2) 11111011
    ~(1<<3) 11110111
    ~(1<<4) 11101111
    ~(1<<5) 11011111
    ~(1<<6) 10111111
    ~(1<<7) 01111111

    不管第n位是0还是1,x和这些数AND 的效果都是将第n位置0.

    看下面的例子,将127的第4位置0

    01111111 (127 in binary)
    & 11101111 (~(1<<4))
    --------
    01101111

    技巧5: 反转第n位

    y = x ^ (1<<n)

    这个玩法同样使用将第n位置1的技巧,但是用的是XOR操作,一个位和另一个位进行XOR,如果它们相同那么结果是0,否则为1.那么怎么切换第n位了? 如果第n位是1,和1 XOR后,1变成0,反之0和1 XOR 结果为1.所以位反转了.

    下面这个例子演示了怎么将01110101 第5位反转:

    01110101
    ^ 00100000
    --------
    01010101

    如果第5位已经是0了呢?

    01010101
    ^ 00100000
    --------
    01110101

    看出端倪了没?XOR两次又还原了. 巧妙的XOR常被用来计算RAID阵列的奇偶校验和简单的加密函数中和其他方面

    技巧6: 将最右边的1置0

    y = x & (x-1)

    终于来点有趣的了! 说实在的,前5个玩法有点无聊.

    这个玩法将最后一个1置0,例如 00101010,变为00101000. 或者00010000,变为0.

    例:

    01010111 (x)
    & 01010110 (x-1)
    --------
    01010110


    01011000 (x)
    & 01010111 (x-1)
    --------
    01010000

    10000000 (x = -128)
    & 01111111 (x-1 = 127 (溢出))
    --------
    00000000


    11111111 (x = 都是1 1)
    & 11111110 (x-1)
    --------
    11111110


    00000000 (x 没有是1的位)
    & 11111111 (x-1)
    --------
    00000000

    那它的原理是呢?

    如果你稍加思索,你就会发现有下面两种情况:

    1. 位列中有1. 减1会将低位变为1并将最后一个1的位变0.这步再和原来的值AND屏蔽了从最后一个1的后面部分.

    2.位列中没有1都是0.这是减去1将溢出所有位都变成1. 和都是0的位列AND 还是0.

    技巧7 拨离最右边的1

    y = x & (-x)

    这个玩法找到最右边的一个1并将其他位置0,例如01010100,转换后为00000100.

    例:

    10111100 (x)
    & 01000100 (-x)
    --------
    00000100


    01110000 (x)
    & 10010000 (-x)
    --------
    00010000

    00000001 (x)
    & 11111111 (-x)
    --------
    00000001

    10000000 (x = -128)
    & 10000000 (-x = -128)
    --------
    10000000

    11111111 (x = all bits one)
    & 00000001 (-x)
    --------
    00000001


    00000000
    & 00000000 (-x)
    --------
    00000000

    这个技巧归功于2的补码,在2的补码中-x和~x+1.

    技巧8: 将右边第一个1后的位全置1

    y = x | (x-1)

    这个最好结合例子来理解. 将01010000 变成 01011111.从右边第一个1开始,后面的0全变成了1.

    这不是一个完美的技巧,当x=0时,结果都是1

    例:

    10111100 (x)
    | 10111011 (x-1)
    --------
    10111111


    01110111 (x)
    | 01110110 (x-1)
    --------
    01110111


    00000001 (x)
    | 00000000 (x-1)
    --------
    00000001


    10000000 (x = -128)
    | 01111111 (x-1 = 127)
    --------
    11111111


    11111111 (x = -1)
    | 11111110 (x-1 = -2)
    --------
    11111111


    00000000 (x)
    | 11111111 (x-1)
    --------
    11111111

    技巧9: 拨离最右边的第一个0

    这个技巧和第7个的相反.找到最右的0,将它置1并将其他位置0. 例如 将10101011,转换成00000100.

    更多的例子:

    --------
    10001000 (~x)
    & 01111000 (x+1)
    --------
    00001000
    00000001 (x)
    --------
    11111110 (~x)
    & 00000010 (x+1)
    --------
    00000010
    10000000 (x = -128)
    --------
    01111111 (~x)
    & 10000001 (x+1)
    --------
    00000001
    11111111 (x = no rightmost 0-bit)
    --------
    00000000 (~x)
    & 00000000 (x+1)
    --------
    00000000

    00000000 (x)
    --------
    11111111 (~x)
    & 00000001 (x+1)
    --------
    00000001

    技巧10: 将最右边0位置1

    y = x | (x+1)

    将最右边的0置1.如将10100011 转换为 10100111.

    更多的例子:

    10111100     (x)
    | 10111101   (x+1)
    --------
    10111101


    01110111     (x)
    | 01111000   (x+1)
    --------
    01111111


    00000001     (x)
    | 00000010   (x+1)
    --------
    00000011


    10000000      (x = -128)
    | 10000001    (x+1)

    --------
    10000001

    11111111 (x = no rightmost 0-bit)
    | 00000000 (x+1)
    --------
    11111111


    00000000 (x)
    | 00000001 (x+1)
    --------
    00000001

    其他内容:

    如果你对其他位操作技巧感兴趣,下面有一些Python,C的代码方便打印8位的符号整数

    Python 代码:

    def int_to_bin(num, bits=8):
    
      r = ''
    
      while bits:
    
        r = ('1' if num&1 else '0') + r
    
        bits = bits - 1
    
        num = num >> 1
    
    print r


    C代码:

    void int_to_bin(int num) {
    
      char str[9] = {0};
    
      int i;
    
      for (i=7; i>=0; i--) {
    
        str[i] = (num&1)?'1':'0';
    
        num >>= 1;
    
      }
    
      printf("%s\n", str);
    
    }
  • 相关阅读:
    ORA02292:integrity constraint(xx) violated child record found 外键关联,无法删除记录
    自定义设置Ext.grid.gridPanel样式
    修改了grid的前3行的颜色为红色
    PLSQL导入导出表的正确步骤
    1000条数据写入到txt文件中,并且做了换行
    webservice 存根方式
    java日期时间
    extjs 页面打开时表格自动加载后台传来的json数据
    处理Clob数据(转)关于oracle中大对象处理的一些方法和实例
    44个提高博客影响力的方法
  • 原文地址:https://www.cnblogs.com/solo/p/2665119.html
Copyright © 2020-2023  润新知