一般情況下,如果要我們寫(xiě)一個(gè)求絕對(duì)值的函數(shù),我們的實(shí)現(xiàn)很有可能會(huì)是這樣:
T abs_Normal(T tNum)
{
if(tNum > 0.0)
return tNum;
else
return -tNum;
}
也就是說(shuō)我們會(huì)用到一個(gè)if-else判斷來(lái)決定是否反轉(zhuǎn)符號(hào)位。在3D游戲軟件,或一些對(duì)性能要求比較高的底層系統(tǒng)中,當(dāng)大規(guī)模的求絕對(duì)值時(shí),這個(gè)if-else結(jié)構(gòu)會(huì)帶來(lái)性能上的損失,那么,如何來(lái)消除if-else結(jié)構(gòu)呢?或許會(huì)有人說(shuō),我們可以用三元操作符啊:
T abs_Normal(T tNum)
{
return tNum > 0.0 ? tNum : -tNum;
}
但是事實(shí)上這是換湯不換藥,因?yàn)槠鋵?shí)質(zhì)上還是存在if-else的判斷的(這應(yīng)該可以從反匯編代碼中看出來(lái))。
我們是通過(guò)位操作來(lái)消除if-else判斷來(lái)求絕對(duì)值。
因?yàn)槭褂梦徊僮鳎覀儾坏貌豢紤]我們操作對(duì)象類(lèi)型的字節(jié)數(shù),下面我將以都是4字節(jié)得float和int為例實(shí)現(xiàn)位操作求絕對(duì)值。
首先,我們有必要了解一下float與int在計(jì)算機(jī)中的內(nèi)部表示方法。
1) float: float即單精度浮點(diǎn)數(shù),"浮點(diǎn)數(shù)"由兩部分組成,即尾數(shù)和階碼。在浮點(diǎn)表示方法中,小數(shù)點(diǎn)的位置是浮動(dòng)的,階碼可取不同的數(shù)值。為了便于計(jì)算機(jī)中小數(shù)點(diǎn)的表示,規(guī)定將浮點(diǎn)數(shù)寫(xiě)成規(guī)格化的形式,即尾數(shù)的絕對(duì)值大于等于0.1并且小于1,從而唯一規(guī)定了小數(shù)點(diǎn)的位置。尾數(shù)的長(zhǎng)度將影響數(shù)的精度,其符號(hào)將決定數(shù)的符號(hào)。浮點(diǎn)數(shù)的階碼相當(dāng)于數(shù)學(xué)中的指數(shù),其大小將決定數(shù)的表示范圍。一個(gè)浮點(diǎn)數(shù)在計(jì)算機(jī)中的表現(xiàn)形式如下:
尾數(shù)符號(hào) 階碼 尾數(shù)有效值
2) int: 用補(bǔ)碼表示,因?yàn)檎麛?shù)的原碼,反碼,補(bǔ)碼都是一樣的,而負(fù)整數(shù)的補(bǔ)碼則是通過(guò)原碼->反碼->補(bǔ)碼轉(zhuǎn)換來(lái)的,所以,-3與3的內(nèi)部表示位差別不僅僅在符號(hào)位
其次,這里先列出兩個(gè)在代碼中用到的宏:
#define INV_SIGN_BIT 0x7fffffff //用來(lái)反轉(zhuǎn)符號(hào)位
#define USE_ASM //是否使用匯編代碼
1 float求絕對(duì)值
知道了float的內(nèi)部表示,我們知道要求其絕對(duì)值,只要將其尾數(shù)符號(hào)位置0即可。這又有下面兩種方法:
1)與:通過(guò)和INV_SIGN_BIT相"與"而將符號(hào)位置0
{
#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
}
注:
1)這里將float轉(zhuǎn)化成int的原因是C語(yǔ)言不支持float的移位操作。
2)移位:通過(guò)先邏輯左移1位,再邏輯右移一位將符號(hào)位置0
{
#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語(yǔ)言的移位操作對(duì)有符號(hào)數(shù)是算術(shù)移位,對(duì)無(wú)符號(hào)數(shù)是邏輯移位。而我們需要的是邏輯移位
2 int求絕對(duì)值
因?yàn)檎偷膬?nèi)部表示是反碼,我們不能簡(jiǎn)單的通過(guò)符號(hào)位置0求絕對(duì)值,下面的算法很好的解決了這個(gè)問(wèn)題:
{
#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)對(duì)于代碼
temp = temp >> 31;
out = out ^ temp;
out = out - temp;
如果iNum是正數(shù):
temp = temp >> 31; //temp = 0
out = out ^ temp; //與0異或不變
out = out - temp; //減0不變
out的結(jié)果就是iNum,即正數(shù)的絕對(duì)值是其本身,沒(méi)問(wèn)題
如果iNum是負(fù)數(shù):
temp = temp >> 31; //temp = oxffffffff
out = out ^ temp; //out為iNum求反
out = out - temp; // 此時(shí)temp = 0xffffffff = -1, 所以out = out + 1
把一個(gè)負(fù)數(shù)的補(bǔ)碼連符號(hào)位求反后再加1,就是其絕對(duì)值了。比如對(duì)于-2來(lái)說(shuō):
原碼 | 反碼 | 補(bǔ)碼 | 補(bǔ)碼全求反 | 再加1 |
備注 |
10000010 | 11111101 | 11111110 | 00000001 | 00000010 |
大家可以看到第一個(gè)與最后一個(gè)數(shù)只有符號(hào)位不同,也就實(shí)現(xiàn)了求其絕對(duì)值。
對(duì)于其他類(lèi)型的數(shù)據(jù)求絕對(duì)值,應(yīng)該 都是大同小異的。這里就不再列舉。