C/C++中移位實現乘除法運算
用移位實現乘除法運算
a=a*4;
b=b/4;
可以改為:
a=a<<2;
b=b>>2;
說明:
除2 = 右移1位 乘2 = 左移1位
除4 = 右移2位 乘4 = 左移2位
除8 = 右移3位 乘8 = 左移3位
... ...
通常如果需要乘以或除以2的n次方,都可以用移位的方法代替。
大部分的C編譯器,用移位的方法得到程式碼比呼叫乘除法子程式生成的程式碼效率高。
實際上,只要是乘以或除以一個整數,均可以用移位的方法得到結果,如:
a=a*9
分析a*9可以拆分成a*(8+1)即a*8+a*1, 因此可以改為: a=(a<<3)+a
a=a*7
分析a*7可以拆分成a*(8-1)即a*8-a*1, 因此可以改為: a=(a<<3)-a
關於除法讀者可以類推, 此略.
迴圈移位差別於一般移位的是移位時沒有數位的丟失。迴圈左移時,用從左邊移出的位填充字的右端,而迴圈右移時,用從右邊移出的位填充字的左側。這種情況在系統程式中時有使用,在一些控制程式中用得也不少。
設有資料說明:
a=01111011,迴圈左移2位 正確結果: 11101101
過程:
b=a>>(8-2) 用來得到正常左移丟失的位和迴圈移位後其正確位置 b=00000001;
a=a<<2;左移 a=11101100
a=a|b; a=11101101
如果不是用中間變數 a=(a>>(8-2))|(a<<2)
總長度N(8 16 32)
迴圈左移n (a>>(N-n))|(a>>n)
迴圈右移n (a<<(N-n))|(a>>n)
C語言的位運算功能是其差別於其他大多數高階程式設計語言的特色之一,用他能方便實現一些特別功能,靈活掌控是用C程式編寫系統程式的基礎。
但是有時候要注意移位指令的越界問題:
#include <stdio.h>
void main()
{
unsigned int i,j;
i=35;
//為什麼下面兩個左移操作結果不一樣?
j=1<<i; // j為8
j=1<<35; // j為0
}
原因是這樣的:i=35;j=1<<i;這兩句在VC沒有做優化的情況下,將被編譯成下面的機器指令:
mov dword ptr [i],23h
mov eax,1
mov ecx,dword ptr [i]
shl eax,cl
mov dword ptr [j],eax
在shl一句中,eax=1,cl=35。而Intel CPU執行shl指令時,會先將cl與31進行and操作,以限制左移的次數小於等於31。因為35 & 31 = 3,所以這樣的指令相當於將1左移3位,結果是8。
而j=1<<35;一句是常數運算,VC即使不做優化,編譯器也會直接計算1<<35的結果。VC編譯器發現35大於31時,就會直接將結果設定為0。這行程式碼編譯產生的機器指令是:
mov dword ptr [j],0
對上面這兩種情況,如果把VC編譯器的優化開關開啟(比如編譯成Release版本),編譯器都會直接將結果設定為0。
所以,在C/C++語言中,移位操作不要超過界限,否則,結果是不可預期的。
下面是Intel文件中關於shl指令限制移位次數的說明:
The destination operand can be a register or a memory location. The count operand can be an immediate value or register CL. The count is masked to 5 bits, which limits the count range to 0 to 31. A special opcode encoding is provided for a count of 1.
可見編譯器的優化選項會對程式結果產生一些不可預料的影響,這在我的另一篇文章中也有所體會。