【这里我们将要讨论一些高级的位操作技巧,如果能适当使用会大大提高你程序的运行速度】
我们已经讨论了一些简单的位操作方法,如果你还不熟悉位操作可以先浏览一下【你必须知道的简单的位操作技巧】,这里我们接着往下说:
技巧6:将最右边的数值为1的bit位设为0
y = x & (x-1)
终于开始有点意思了,技巧1到技巧5说实话是有点小儿科。
这条语句将从右至左看值为1的比特位置为0。例如:整数00101010经过上述操作变成00101000,整数00010000经过操作变成0,因为只有一个比特位值为1。
更多的例子:
` 01010111 (x) & 01010110 (x-1) -------- 01010110 01011000 (x) & 01010111 (x-1) -------- 01010000 10000000 (x = -128) & 01111111 (x-1 = 127 (with overflow)) -------- 00000000 11111111 (x = all bits 1) & 11111110 (x-1) -------- 11111110 00000000 (x = no rightmost 1-bits) & 11111111 (x-1) -------- 00000000
观察这些例子你会发现有两种情况:
1、这个数值存在值为1的比特位,减一就会将值为一的比特位右边的低位bit全置为1,自身变成0,再与原来的数与运算,得到的结果就是这一位置为0。
2、这个数值是0,那么没有值为1的比特位,减一造成下溢出,所有比特位全变成1(有符号的整数),全0和全1做与运算结果为0。
技巧7:隔离最右边值为1的比特位
y = x & (-x)
也就是找到一个数的最右边值为1的比特位,将其他位置为0。上面的语句实现的就是这个功能。例如01010100(黑体是最右边值为1的比特位)运算后结果为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 (x = all bits 0, no rightmost 1-bit) & 00000000 (-x) -------- 00000000
这项操作在补码表示的范围内有效,在二进制补码系统中-x表示为-x+1。
我们再分成两种情况来看:
1、存在最右边的值为1的比特位,我们以这位比特位中心(暂时标记为bi),把其它比特分为左右两边,右边的所有比特位都为0(bi-1,…,b0),左边的比特位不知道是啥(bn,…,bi+1)。
好了,现在求-x,首先,将bi变位0,然后将bi-1,…,b0变位1,接着翻转bn,…,bi+1,最后再将所得结果+1。就得到了补码形式的-x。
在+1之前由于bi-1,…,b0位都为1,加一后都为0,直到遇到bi位。
总的来看,计算-x即是翻转bn,…,bi+1位,bi位不变,bi-1,…,b0都变成0。
现在来看x & (-x)就很清楚了,即是将bn,…,bi+1置为0,bi位不变,bi-1,…,b0置为0。
2、不存在值为1的比特位,值为0,0的二进制补码还是0,与运算结果还是0.
我们严格阐述了这个技巧是正确的。
技巧8:右传播最右边值为1的比特位
y = x | (x-1)
看一下例子就知道是怎么回事了,数01010000经过运算的到,寻找最右边值为1的比特位,然后将这一位右边的所有比特位置为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
尽管不能像前一个技巧那样严格,我们还是来简单证明一下(详细论述浪费大家时间,这也不是学术论文不是)。两种情况,从简单的开始:
1、不存在最右边的值为1的比特位,这种情况x=0,x-1=-1,-1的二进制补码表示为11111111(好像忘了说,我们讨论的数都在8bit范围,也不失一般性),0与11111111或运算结果是11111111(不是想要的结果,但事实很残酷)。
2、存在最右边的值为1的比特位,我们故计重施,还是分成两部分(和前一个例子一样),计算x-1只影响右边的比特位,将bi位置为0,所有右边的置为1。现在将结果x-1与小做或运算,左边部分的比特位不变,bi位还是1,右边的都变成了1。结果就是最右边的值为1的比特位向右传播了。
技巧9:隔离最右边值为0的比特位
y = ~x & (x+1)
和技巧7刚好相反,找到x最右边的值为0的比特位,将其他位置为0,这一位置为1。例如x=10101011(找到黑体的0),运算结果是00000100。
看下面更多的例子:
` 10111100 (x) -------- 01000011 (~x) & 10111101 (x+1) -------- 00000001 01110111 (x) -------- 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
简单证明:假设存在最右边值为0的比特位,那么-x和x+1将这一位变位1,-x和x+1的与运算将左边部分的比特位置为0(-x将x左边部分取反),右边部分运算结果也为0(x+1将x右边部分变位0),因此只剩下bi位为1。
y = x | (x+1)
` 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
事实上,x和x+1进行或运算并不丢失任何信息,将x加1只是填充了最右边的一个0,结果是max{x,x+1}。当x+1溢出时,结果是0,x并没有值为0的比特位,如果没有溢出结果就是x+1,或运算后最右边的0比特被置为1。