位运算技巧2

 

题目:一个数组中有多个整数,其中只有一个没有重复过,求出该数:算法

考虑使用异或操做帮助实现。数组

 

int AppearOnce(int data[], int length)
{
    int i;
    int once = 0;
    for (i = 0; i < length; i++)
    {
        once ^= data[i];
    }
    return once;
}

隐含原理,只有一个没有重复过,其余的都重复过,说明数的个数为2N+1函数

N对相同的数异或,结果为0,如:性能

a^b^a^b=0;this

0^x=x;spa

0跟任何数异或仍是自身。rest

 

题目:用位运算实现求绝对值-有效避开if-else判断code

By SmartPtr(http://www.cppblog.com/SmartPtr/)

orm

通常状况下,若是要咱们写一个求绝对值的函数,咱们的实现颇有可能会是这样:对象

template < class  T >
T abs_Normal(T tNum)
{
if (tNum  > 0.0 )
return  tNum;
else
return - tNum;
}

也就是说咱们会用到一个if-else判断来决定是否反转符号位。在3D游戏软件,或一些对性能要求比较高的底层系统中,当大规模的求绝对值时,这个if-else结构会带来性能上的损失,那么,如何来消除if-else结构呢?或许会有人说,咱们能够用三元操做符啊:

template < class  T >
T abs_Normal(T tNum)
{
return  tNum  > 0.0 ?  tNum :  - tNum;
}

可是事实上这是换汤不换药,由于其实质上仍是存在if-else的判断的(这应该能够从反汇编代码中看出来)。

咱们是经过位操做来消除if-else判断来求绝对值。

由于使用位操做,咱们不得不考虑咱们操做对象类型的字节数,下面我将以都是4字节得float和int为例实现位操做求绝对值。
首先,咱们有必要了解一下float与int在计算机中的内部表示方法。
1) float: float即单精度浮点数,"浮点数"由两部分组成,即尾数和阶码。在浮点表示方法中,小数点的位置是浮动的,阶码可取不一样的数值。为了便于计算机中小数点的表示,规定将浮点数写成规格化的形式,即尾数的绝对值大于等于0.1而且小于1,从而惟一规定了小数点的位置。尾数的长度将影响数的精度,其符号将决定数的符号。浮点数的阶码至关于数学中的指数,其大小将决定数的表示范围。一个浮点数在计算机中的表现形式以下:
尾数符号 阶码 尾数有效值

S          E               M

s是符号位,占1位,为0表示正。

M尾数:对于32位浮点数来讲,占用23位。E是阶码,占用8位。

2) int: 用补码表示,由于正整数的原码,反码,补码都是同样的,而负整数的补码则是经过原码->反码->补码转换来的,因此,-3与3的内部表示位差异不单单在符号位
其次,这里先列出两个在代码中用到的宏:
#define INV_SIGN_BIT 0x7fffffff //用来反转符号位
#define USE_ASM         //是否使用汇编代码

1 float求绝对值
知道了float的内部表示,咱们知道要求其绝对值,只要将其尾数符号位置0便可。这又有下面两种方法:
1)与:经过和INV_SIGN_BIT相"与"而将符号位置0

inline  float  Fabs_and( float  fNum)
{
#ifdef USE_ASM
float  fOut;
   __asm
   {
       MOV EAX, fNum;
       AND EAX, INV_SIGN_BIT; 
// set the sign bit to 0 by AND
       MOV fOut, EAX;
   }
return  fOut;
#else
int* temp = (int*)& fNum; //取地址
int out=*temp & INV_SIGN_BIT;
return *((float*)&out
);
#endif
或者:

float absFloat(float a)
{
int p=*((int*)&a);
int out=(p)&0x7fffffff;
return *((float*)&out);

}


}(我的注:经验证,以上代码确实能够,上面的代码很巧妙,不能是
int tmp=(int)&fnum;不然,tmp只是截取了整数部分)

注:
1)这里将float转化成int的缘由是C语言不支持float的移位操做

2)移位:经过先逻辑左移1位,再逻辑右移一位将符号位置0

inline  float  Fabs_shift( float  fNum)
{
#ifdef USE_ASM
float  fOut  = 0 ;
   __asm
   {
       MOV EAX, fNum;
       SHL EAX, 
1 // set the sign bit to 0 by shift left then right
       SHR EAX,  1 ;
       MOV fOut, EAX;
   }
return  fOut;  
#else
   unsigned 
int *  temp  =  (unsigned  int * ) & fNum;
   unsigned 
int out = * temp;

out = out << 1 ;
out = out >> 1 ;

return * (( float * ) & out );
#endif
}

注:
1)这里使用unsigned int的缘由是C语言的移位操做对有符号数是算术移位,对无符号数是逻辑移位。而咱们须要的是逻辑移位

2 int求绝对值
由于整型的内部表示是反码,咱们不能简单的经过符号位置0求绝对值,下面的算法很好的解决了这个问题:

inline  int  Abs_bit( int  iNum )
{
#ifdef USE_ASM
int  iOut  = 0 ;
   __asm
   {
       MOV EAX, iNum;
       MOV EDX, EAX;
       SAR EDX, 
31 ;    // all of edx's bit are eax's sign bit: 000.. or 111
       XOR EAX, EDX;  // this interesting algorithm help to avoid "if else" structure
       SUB EAX, EDX;
       MOV iOut, EAX;
   }
return  iOut;
#else

int  out =  iNum;
int  temp  =  iNum;
   temp 
=  temp  >> 31 ;

out = out ^  temp;
out = out -  temp;

return out ;

#endif
}

注:
1)对于代码
        temp = temp >> 31;
        out = out ^ temp;
        out = out - temp;
若是iNum是正数:
        temp = temp >> 31; //temp = 0
        out = out ^ temp; //与0异或不变
        out = out - temp; //减0不变

out的结果就是iNum,即正数的绝对值是其自己,没问题

若是iNum是负数:
        temp = temp >> 31; //temp = oxffffffff
        out = out ^ temp; //out为iNum求反     (利用异或取反)

 


        out = out - temp; // 此时temp = 0xffffffff = -1, 因此out = out + 1
把一个负数的补码连符号位求反后再加1,就是其绝对值了。好比对于-2来讲:

原码 反码 补码 补码全求反 再加1 备注
10000010 11111101 11111110 00000001 00000010  


你们能够看到第一个与最后一个数只有符号位不一样,也就实现了求其绝对值。

(之前作过的一道题目,对一个数[y]补,获得其[-y]补:

int negative(int a)//取补码,咱们输入x,会输出-x。
{
return add(~a,1);
}


对于其余类型的数据求绝对值,应该 都是大同小异的。这里就再也不列举。

相关文章
相关标签/搜索