有趣的位运算(一)基本操作
在c/c++语言中,适当的使用位运算来代替分支判断,可以去除程序中的分支预测,有效的提高程序的执行效率。讲解位运算最著名的要数<<hack's delight>>,
不过网上也有很多牛人总结过位运算心得,位运算无非就是利用数字(通常为整数)的补码表示进行的与,或,非,异或,四大操作,下面是我总结的有趣且常用的一些位运算:
一.最简单的:
1)判断奇偶数:(x & 1) == 0):even, ((x & 1) == 1) : odd
2)判断低n 个bit位是否为1:
y=x & (1<<n)
这里通过将正整数1(补码表示为..00001)逻辑左移n位以后,与x进行与运算,来判断第n位的情况,注意,移位计数是从0开始的。即1可以左移0位,1位...n位。
3)则我们可以通过
y=x |(1<<n)
来将x的第n个bit位置为1(set).
4)沿着上面的思路,我们可以将1逻辑左移n位后,按位取反,然后再与x相与,即可将x的右属第n位置位0(unset).
y=x &(~(1<<n) )
5)同样呢,我们可以把与运算符变成异或运算,就可以实现将x的右数第n个bit位取反(toggle)的效果,即:
y = x ^ (1<<n)
6) 通常,对于一个整数x,x-1是将x的bit表示的最右侧的10000的形式变成了01111的形式,如:
01010111 (x) -> 01010110 (x-1) 10000000 (x = -128) -> 01111111 (x-1 = 127 (with overflow))
这样,我们就找到了将x的最右侧的bit 1置位0的方法,
y=x&(x-1)
7)根据6)我们可以得到奖x的最右侧的bit 1的右侧的0全部置位1的方法:
y = x | (x-1)
8)在补码表示中,对于一个数x, -x=~x+1,
即:
x:10111100 ~x:01000011 -x:01000100
则,我们可以用
y=x&(-x)
来提取x的bit表示中的最有侧的1,而将其他为都置位0.
9)根据8),我们可以用
y = ~x & (x+1)
将x的最右侧的0提取出来,置为1,其余的位全部置位0.原理是,~x使x的最右侧的0变为1,其余的比特位一次反转;x+1同样使得最右侧0变为1,这样在,两者相与以后,只会有此位为1,其余位都
变为0.
二.高阶的
以下的高阶的位运算来自这里,是斯坦福大学的一位教授所整理的,讨论了代码的可移植性以及效率问题,非常nice!
挑一些个人感觉有趣的说明如下:
1)判断一个数的正负:
const int CHAR_BIT =8;//此处为了易于说明,为本人加入 int v; // we want to find the sign of v int sign; // the result goes here // CHAR_BIT is the number of bits per byte (normally 8). sign = -(v < 0); // if v < 0 then -1, else 0. // or, to avoid branching on CPUs with flag registers (IA32): sign = -(int)((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1)); // or, for one less instruction (but not portable): sign = v >> (sizeof(int) * CHAR_BIT - 1);
上面代码给出了3中方法来查看整数v的符号,第一个sign = -(v < 0),即看v是否小于0,为真则sign=-1,反之则为0,含有小于号,在有的平台上可能会出现条件分支,故紧接着给出了去除分支的方法:
a: sign = -(int)((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1))和
b:sign = v >> (sizeof(int) * CHAR_BIT - 1);
采用的思想是算术右移(sizeof(int) * CHAR_BIT - 1)个整数位,使得符号位跑到最右侧,若v为负数,则sign=-1,否则位0。
这里作者又说了,a式代码是可一直的,b式的代码是不可移植的,a式进行了强制类型转换,但具体原因是什么,目前本人还没弄懂(感觉是针对老板的c语言说的,即1989 ANSI C)。
当然,根据上式,我们可以把结果写的更明确些,如:
sign = +1 | (v >> (sizeof(int) * CHAR_BIT - 1)); // if v < 0 then -1, else +1
返回值为-1或1.
或者:
sign = (v != 0) | -(int)((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1)); // Or, for more speed but less portability: sign = (v != 0) | (v >> (sizeof(int) * CHAR_BIT - 1)); // -1, 0, or +1
返回值为-1,0,1.
如果小于号可以被编译器优化的话,则还有:
sign = (v > 0) - (v < 0); // -1, 0, or +1
最后,当我们判断v是否是非负数时,即v>0,返回1,其余返回0,则这里异或运算就派上用场了:
sign = 1 ^ ((unsigned int)v >> (sizeof(int) * CHAR_BIT - 1)); // if v < 0 then 0, else 1
2)判断两个整数是否异号:
int x, y; // input values to compare signs bool f = ((x ^ y) < 0); // true iff x and y have opposite signs
当然,跟1)式,我们也可以去除比较符号:
int x, y; // input values to compare signs int f = ((x ^ y) >> (sizeof(int) * CHAR_BIT - 1))); // true iff x and y have opposite signs
3)计算整数的绝对值:
int v; // we want to find the absolute value of v unsigned int r; // the result goes here int const mask = v >> sizeof(int) * CHAR_BIT - 1; r = (v + mask) ^ mask;
这里如果v位整数,则mask值为0,r=v;如果v位负数,mask为全1,v+mask使得v最右侧的1及其此位右侧的0全部取反,这详单与对原数v按位取反,即,(+v+mask)=~(+v),然后呢,~(+v)与掩码mask相异或,即使将v的各比特位按位取反,最后得到+v.
另一个可选的方法是:
r = (v ^ mask) - mask;
4)计算两个整数中的最大最小值:
int x; // we want to find the minimum of x and y int y; int r; // the result goes here r = y ^ ((x ^ y) & -(x < y)); // min(x, y) r = x ^ ((x ^ y) & -(x < y)); // max(x, y)
对于min(x,y),当x<y时,-(x<y)为-1,即11..111,这样就变成了,r=y ^ (x ^ y)=x, max(x,y)的计算方法类似。
在一些没有条件转移指令的机器上,可以采用以上方法来计算最大最小值,如果有条件转移指令,则可以:
r=x>y?y:x;//min(x,y)
这样也可以去除分支。可以参考体系结构类的书籍,如《深入理解计算机系统》,或这里.
5)判断一个数是否为2的n次幂:
unsigned int v; // we want to see if v is a power of 2 bool f; // the result goes here f = (v & (v - 1)) == 0;
当我们要考虑v是否为0时(0不是2的幂),可以得到:
f = v && !(v & (v - 1));
6)计算一个数含有多少个比特1:
unsigned int v; // count the number of bits set in v unsigned int c; // c accumulates the total bits set in v for (c = 0; v; c++) { v &= v - 1; // clear the least significant bit set }
此题在<<编程之美>>中叶出现过,还有其他方法,如查表法等。
7)反转一个数的比特位,即1110001变成1000111。
unsigned int v; // input bits to be reversed unsigned int r = v; // r will be reversed bits of v; first get LSB of v int s = sizeof(v) * CHAR_BIT - 1; // extra shift needed at end for (v >>= 1; v; v >>= 1) { r <<= 1; r |= v & 1; s--; } r <<= s; // shift when v's highest bits are zero
思路是,每次取得v最右侧的一个比特位以后,放在r的最右侧,然后v右移,r左移。最后考虑到,当v位整数时,后面会出现一串的0,导致循环结束,这样,就要将r在左移相应的位数,即s位。当然还有很多方法
来实现位的反转,这是最简单易于理解的一种。
另外还有:
unsigned int v; // 32-bit word to reverse bit order // swap odd and even bits v = ((v >> 1) & 0x55555555) | ((v & 0x55555555) << 1); // swap consecutive pairs v = ((v >> 2) & 0x33333333) | ((v & 0x33333333) << 2); // swap nibbles ... v = ((v >> 4) & 0x0F0F0F0F) | ((v & 0x0F0F0F0F) << 4); // swap bytes v = ((v >> 8) & 0x00FF00FF) | ((v & 0x00FF00FF) << 8); // swap 2-byte long pairs v = ( v >> 16 ) | ( v << 16);
以及
unsigned int s = sizeof(v) * CHAR_BIT; // bit size; must be power of 2 unsigned int mask = ~0; while ((s >>= 1) > 0) { mask ^= (mask << s); v = ((v >> s) & mask) | ((v << s) & ~mask); }
浙公网安备 33010602011771号