高级位操作技巧

【这里我们将要讨论一些高级的位操作技巧,如果能适当使用会大大提高你程序的运行速度】

我们已经讨论了一些简单的位操作方法,如果你还不熟悉位操作可以先浏览一下【你必须知道的简单的位操作技巧】,这里我们接着往下说:

技巧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)
例如x=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

事实上,x和x+1进行或运算并不丢失任何信息,将x加1只是填充了最右边的一个0,结果是max{x,x+1}。当x+1溢出时,结果是0,x并没有值为0的比特位,如果没有溢出结果就是x+1,或运算后最右边的0比特被置为1。

Copyright © 2015 programnote
原文地址:https://www.cnblogs.com/programnote/p/4689303.html