位运算整理(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));
浙公网安备 33010602011771号