位運算技巧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);
}


對於其餘類型的數據求絕對值,應該 都是大同小異的。這裏就再也不列舉。

相關文章
相關標籤/搜索