位运算整理(1)

关于操作计算方法

在这里计算算法的总运算次数时,将任何C运算符都算作一次运算。不需要写到RAM中的中间作业不计算在内。当然,这种操作计算方法只能作为实际机器指令数和CPU时间的近似值。所有的操作都被假定为需要相同的时间,实际情况并非如此,但随着时间的推移,CPU越来越朝着这个方向发展。有许多细微的差别决定了系统运行给定样本代码的速度,比如缓存大小、内存带宽、指令集等。最后,基准测试是确定一种方法是否真的比另一种方法更快的最佳方法,因此将以下技术视为在目标架构上测试的可能性。

计算一个整数的符号

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); 

上面最后一个表达式对于32位整数的计算结果是sign = v >> 31。这比显而易见的方法--sign = -(v < 0)--快了一个操作。这个技巧之所以有效,是因为当有符号的整数被右移时,最左边位的值会被复制到其他位。当值为负数时,最左边位为1,否则为0;所有的1位给出-1。不幸的是,这种行为是架构特定的。

或者,如果你希望结果是-1或+1,则使用:

sign = +1 | (v >> (sizeof(int) * CHAR_BIT - 1));  // if v < 0 then -1, else +1

另一方面,如果您希望结果是-1、0或+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
// Or, for portability, brevity, and (perhaps) speed:
sign = (v > 0) - (v < 0); // -1, 0, or +1

如果你想知道某事是非负数,结果是+1还是0,那就用:

sign = 1 ^ ((unsigned int)v >> (sizeof(int) * CHAR_BIT - 1)); // if v < 0 then 0, else 1

Rohit Garg 在 2009 年 9 月 12 日建议使用非负整数的版本。

检测两个整数是否有相反的符号

int x, y;               // input values to compare signs

bool f = ((x ^ y) < 0); // true iff x and y have opposite signs

计算整数绝对值(abs),不需要分支

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;

变种:

r = (v ^ mask) - mask;

有些CPU没有整数绝对值指令(或者编译器无法使用它们)。在分支昂贵的机器上,上述表达式可以比明显的方法r = (v <0) ? -(unsigned)v : v,尽管操作次数是一样的。

计算两个整数的最小(min)或最大(max),不需要分支

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 < y) ? x : y更快,尽管它涉及到更多的两条指令。(通常情况下,显而易见的方法是最好的。)它的工作原理是,如果x < y,那么-(x < y)将全部是1,所以r = y ^ (x ^ y) & ~0 = y ^ x ^ y = x.否则,如果x >= y,那么-(x < y)将全部是0,所以r = y ^ ((x ^ y) & 0) = y.在某些机器上,将(x < y)评估为0或1需要一个分支指令,所以可能没有优势。

要找到最大值,使用:

r = x ^ ((x ^ y) & -(x < y)); // max(x, y)

快速版本:

如果你知道INT_MIN <= x - y <= INT_MAX,那么你可以使用下面的方法,这样会更快,因为(x - y)只需要评估一次。

r = y + ((x - y) & ((x - y) >> (sizeof(int) * CHAR_BIT - 1))); // min(x, y)
r = x - ((x - y) & ((x - y) >> (sizeof(int) * CHAR_BIT - 1))); // max(x, y)

请注意,1989年的ANSI C规范并没有规定有符号的右移的结果,所以这些都是不可移植的。如果在溢出时抛出异常,那么x和y的值应该是无符号的,或者减法的值应该转为无符号的,以避免不必要的抛出异常,然而右移需要一个有符号的操作数来产生负数时的所有1位,所以在那里转为有符号的。

2009年6月2日,Timothy B. Terriberry建议使用xor而不是加法和减法,以避免浇铸和溢出的风险。

判断一个整数是否是2的次幂

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;

注意,这里的0被错误地认为是2的幂。要纠正这种情况,请使用:

f = v && !(v & (v - 1));

posted on 2020-12-08 14:49  QzZq  阅读(96)  评论(0)    收藏  举报

导航