【面试题】计算整数符号

题目:计算一个整数的符号位

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在执行时,前面的各种流程已经在执行下面一条乃至多条的准备工作。

posted on 2013-03-09 20:26  zhuyf87  阅读(473)  评论(0编辑  收藏  举报

导航