题目:计算一个整数的符号位
int v; // we want to find the sign of v int sign; // the result goes here
不能使用函数,不能使用if,不能使用“?:”三目运算符…
总而言之,本题主要考查的是“位操作、逻辑操作、关系操作”。在google里面输入“Compute the sign of an integer”,搜到一篇强文,里面也包括了这道题目的答案。
地址:http://graphics.stanford.edu/~seander/bithacks.html
这里把文中关于本题的解答引用如下:
Compute the sign of an integer
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);
The last expression above evaluates to sign = v >> 31 for 32-bit integers. This is one operation faster than the obvious way, sign = -(v < 0). This trick works because when signed integers are shifted right, the value of the far left bit is copied to the other bits(算术右移). The far left bit is 1 when the value is negative and 0 otherwise; all 1 bits gives -1. Unfortunately, this behavior is architecture-specific.
Alternatively(或者), if you prefer the result be either -1 or +1, then use:
sign = +1 | (v >> (sizeof(int) * CHAR_BIT - 1)); // if v < 0 then -1, else +1
On the other hand, if you prefer the result be either -1, 0, or +1, then use:
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
If instead you want to know if something is non-negative, resulting in +1 or else 0, then use:
sign = 1 ^ ((unsigned int)v >> (sizeof(int) * CHAR_BIT - 1)); // if v < 0 then 0, else 1
Caveat: On March 7, 2003, Angus Duggan pointed out that the 1989 ANSI C specification leaves the result of signed right-shift implementation-defined, so on some systems this hack might not work. For greater portability, Toby Speight suggested on September 28, 2005 that CHAR_BIT be used here and throughout rather than assuming bytes were 8 bits long. Angus recommended the more portable versions above, involving casting on March 4, 2006. Rohit Garg suggested the version for non-negative integers on September 12, 2009.
刚开始对:“sign = -(int)((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1));” 这行代码为什么进行(unsigned int)类型转换不理解。后来自己想明白了:
sign = -(int)((unsigned int)((int)v) >> (sizeof(int) * CHAR_BIT - 1)); (1)
是针对下面的
sign = v >> (sizeof(int) * CHAR_BIT - 1);(2)
来说的。
(2)式如果想得到正确的结果,必须保证“>>”操作执行的是算数右移。也就是右移时,高位补符号位。
这样如果是负数的话,最高位为1,右移之后结果为(假设int占32 bits):0xFFFFFFFF,也就是结果为-1(0xFFFFFFFF是-1的补码)。
但并不是所有平台的所有编译器的“>>”操作都是算数右移,有些是逻辑右移。也就是对于有符号数,右移时高位补充的是0。这样(2)式就得不到正确的结果了(对于负数,最终表达式的结果却是1)。所以(2)式上面有注释:
// or, for one less instruction (but not portable):少一条指令,但不可移植。
而(1)式,(unsigned int)((int)v) ,先将v转换为无符号数,这样c/c++标准已经规定,对于无符号数,“>>”操作高位补的肯定是0。这样右移(sizeof(int) * CHAR_BIT - 1)之后,负数得到的结果就是1,0或者正数得到的结果就是0。再加上前面的负号,这样负数的最终结果就是-1,正数和0的最终结果就是0。
也就是: // if v < 0 then -1, else 0.
// or, to avoid branching on CPUs with flag registers (IA32):
这句注释刚开始没理解,后来在csdn开贴,这里贴出比较靠谱的答案。
mujiok2003:
mymtom:
branching 这里指的是分支(也就是条件跳转),由于条件跳转会严重影响CPU程序的速度,所以这里说要avoid branching。
CandPointer:
分支预测错误, 则整个流水线废了。For Pentium 4 and Intel Xeon processors, the branch delay for a correctly predicted instruction can be as few as zero clock cycles. The branch delay for a mispredicted branch can be many cycles, usually equivalent to the pipeline depth.
所以要避免分支。参见上图,流水线,解码,某port在执行时,前面的各种流程已经在执行下面一条乃至多条的准备工作。