今天在Matrix67大牛的博客上看到了很多位运算的优化技巧,顿感位运算的神奇,现在来我总结一下位运算的神奇用法。
下面是常见的位运算:
a & b a和b都是1的位取1,其他都取0。和&&有区别,&&是逻辑运算,返回true或者false
a | b a和b都是0的位取0,其余情况都取1。和||有区别,||是逻辑运算,返回true或者false
a ^ b a和b相同的位取0,不同的位取1.
~a a按位取反 一个无符号整形取反等于表示上限减去这个数, 比如12 假设是16位无符号整型,~12就是65535-12=65523
a << b a左移b位
a >> b a右移b位
下面是位运算的几种常见的用法:
把a的末k位变成1 00110001 k=3 => 0110111 a | (1<<k - 1)
把a的末k位取反 00110001 k=3 => 0110110 a ^ (1<<k -1)
把a右边连续的1变成0 00110111 => 00110000 (a +1)&a
把右边连续的0变成1 00101111 => 00100000 (a - 1)^a
取右边连续的1 00101111 => 1111 ((a+1) | a) >> 1
去掉右起第一个1的左边 00101000 => x & (-x) 或者 x & (x ^ (x-1)) 这个在树状数组中会用到
上面这些只是位运算的简单应用(已经非常强大了),还有一些更加强大的用处,第一个例子是,求一个32bit整数,求它的1的位数,代码如下:
x := (x and $55555555) + ((x shr 1) and $55555555);
x := (x and $33333333) + ((x shr 2) and $33333333);
x := (x and $0F0F0F0F) + ((x shr 4) and $0F0F0F0F);
x := (x and $00FF00FF) + ((x shr 8) and $00FF00FF);
x := (x and $0000FFFF) + ((x shr 16) and $0000FFFF);
想一下怎么回事,比如x为21, 二进制为10101,把这个数代入代码中看下,
第一步 : 10101 & 0101010101010101 + (1010)&0101010101 = 10101
第二步 :10101&001100110011 + (101)&001100110011 = 10001+1 = 10010
第三步: 10010&00001111 + 1&00001111 = 10+1= 11
第四步: 11&11111111 + 0 = 11
第五步: 11& 11111111 + 0 = 11
上面五步得到了最终结果,答案为3,到这里,看出什么了吗?这段代码其实是用了分治的思想,我看了好久,这几个数字比较令人眼花。 下面让我来解释一下。比如这样一个数字,x32x31x30...x4x3x2x1 , 第一步之后,变成了 x32x31两位表示x32和x31的和,x30x29表示x30和x29的和...以此类推。第二步之后,x32x31x30x29四位数字表示x32~x29的和,x28x27x26x25表示x28~x25四位数字的和,以此类推。第五步结束后x32~x1表示x32~x1这32位数字的和。如果你还是不清楚,请看下面这个图:
如何只用位运算来求一个整数的绝对值呢? 一个正整数x和它的相反数-x的关系是,-x是x的补码,即-x=(~x)+1.x的绝对值代码为 x ^ (~(x >> 31) + 1) + x >> 31,当x为正数时,x>>31为0,取反变成-1,再加1变成0,0异或任何数不变;当x为负数时候,x>>31=1,1取反后加一变成0xFFFFFFFF,异或x之后,x每位取反,取反后再加一刚好是相反数。
先写到这里,用到了再加