很多人都知道,整數變數乘上常數的運算我們可以通過移位和加減法運算來代替,比如計算X*2,可以通過X+X或X<<1來替換而X*9,可以通過(X<<3)+X來替換X*7可以通過(X<<3)-X來替換,等等.由於加法和移位元運算在電腦中遠遠快於乘除運算,所以通過這種替換,運算速度可以得到極大的提高.現在一般的編譯器都能夠支援這種最佳化的,所以在寫代碼時一般情況我們沒有必要自己做這種最佳化,讓編譯器來做就可以了. 可是很少人知道,實際上整數變數除以常數的運算也可以轉變為乘法運算從而可以轉化為移位和加減運算.比如使用gcc –O3對下面代碼進行最佳化int f(int x){ return x/10;}編譯出來的代碼是 pushl %ebp movl %esp,%ebp movl 8(%ebp),%ecx movl $1717986919,%edx movl %ecx,%eax imull %edx sarl $2,%edx movl %ecx,%eax sarl $31,%eax subl %eax,%edx movl %edx,%eax movl %ebp,%esp popl %ebp ret也就是:long long v=(long long)x*1717986919;w=(v>>34)-(x>>31);return w;是不是很神奇呀?讓我們看看這個到底是怎麼做到的呢?讓我們考慮一般的情況,我們要計算x/A,其中x是變數,A是一個常數.如果A是2的冪,那麼x/A直接就可以轉化為移位元運算,比如x/4可以轉化為x>>2.下面假設A不是2的冪.我們可以先找一個比A大的2的冪L (L遠遠大於A)我們知道,在浮點計算中,有x/A=x*(L/A)/L如果我們讓b=L/A, B=L/A+1 (整數除法,去掉小數點那麼我們就可以知道
x*b/L<=x/A<=x*B/L (整數除法)那麼如果x*B/L和x*b/L相同的化,它們就等於x/A了.我們在浮點範圍考察
x*B/L-x*b/L=x/L如果x/L<1/A,那麼我們知道,x/A<=x*B/L<x/A+1/A=(x+1)/A,那麼我們必然可以用x*B/L來代替x/A.所以實際上我們選擇L>=x*A就可以了,由x的任意性,選擇L>=2^32*A就可以了所以我們可以選擇L=2^(32+bit of A),然後計算出B=L/A+1.不過這種演算法有一個問題是B將大於2^32,不利於計算.一種更細緻的分析可以得出,當x>=L/([L/A]*A+A-L)時結論成立(也就是讓b=L/A,B=[L/A]+1得出的結果,這裡除法是浮點除法,[]表示取整)所以如果L/([L/A]*A+A-L)>=2^32就可以了.或者說L/(A-L%A)>=2^32就可以了通過這種方法,我們就有可能找到一個B=[L/A]<2^32,(其中L是2的冪)比如取A=10,取L=2^35=34359738368,那麼就有
L/(A-L%A)=2^35/(10-8)=2^34>2^32,所以可行,於是
B=[L/A]+1= 3435973837於是我們可以產生下面的代碼(X*3435973837)>>35如果x換成有符號整數,那麼我們只要求B=[L/A]<=2^31就可以了,這時我們取L=2^34=17179869184就可以了,B=[L/A]+1= 1717986919於是我們可以產生代碼(X*1717986919)>>34可是這個代碼只對X>=0成立,如果對於X<0,我們還需要對結果簡單的調整一下,這個就是gcc最終產生的代碼多了一個(X>>31)的原因(其實就是(X>=0)?0:-1)