從匯編代碼中探究整數(shù)的取余運算
前言
之前那一篇文章里面探討了整數(shù)的除法運算,今天我們來再看一下整數(shù)的取余運算(modulo);取余運算中,取余的那個數(shù)(也就是%后面的那個數(shù))正負號無所謂,所以下文都只談取余數(shù)為正數(shù)的情況。
涉及取余數(shù)正負號的情況,只出現(xiàn)在當(dāng)我們兩個數(shù)的數(shù)據(jù)類型不一樣的時候,比方說unsigned數(shù)據(jù)取余一個signed的數(shù)據(jù),但本文不會談及這種情況(因為沒見過有人這么用)

一、n%1
n%1當(dāng)然結(jié)果是0,就連我們沒有優(yōu)化的-O0都知道:
只不過在這里用的是mov這個指令,而xor會更快一點。

二、n取余2的平方
我們先假設(shè)n為一個unsigned類型的數(shù)據(jù)。
我們知道n%2只會有2種結(jié)果,要么是1(n為奇數(shù))要么是0(n為偶數(shù)),我們的編譯器自然也知道(準確的說是寫編譯器的人自然也知道),所以會生成以下的匯編代碼:
這里是一個非常神奇的and運算(注意,這里的1是十進制下的1),這里的這個and相當(dāng)于一個mask;我們只需要看最后一個bit,如果最后一位是1(奇數(shù)),那么我們就得到1,如果最后一位是0(偶數(shù)),那么我們就得到0
我們?nèi)∮?的p次方時,我們只需要看最后的p個bit,比方說n%8,那么我們就看最后的3個bit,于是就有了:
接下來我們取消我們最初的那個假設(shè),這個時候,n%2會給我們?nèi)缦碌拇a:
這個代碼突然變得非常復(fù)雜,因為我們需要考慮負數(shù)的情況。上面這個代碼告訴我們,當(dāng)我們只需要用到非負數(shù)取余的時候(比如說loop一個數(shù)組),我們最好指明我們的數(shù)據(jù)類型為unsigned
接下來,我們還是將上面的代碼轉(zhuǎn)化為公式:
還是和前文除法運算一樣,我們見到了nSHR31這個值,直覺告訴我們這個是運用在負數(shù)的情況下的。我們在這里拿n=-1做一個例子,如果我們還是用最上面的那個and運算的話,我們最終得到的結(jié)果將會是
居然變成一個正數(shù)了??
但是如果我們用上述的公式的話,我們首先-1+1=0,然后0and1得到0,最后減去1得到-1。這里我們得到了一個正確的值-1。你還可以假設(shè)n為其他負數(shù),然后自己算算看,

三、n取余其他整數(shù)
n取余其他整數(shù)的時候,實際上我們是先做一個除法運算,然后再做一個減法運算
比如說n%3我們會得到如下的代碼
前四行我們已經(jīng)在之前的除法運算一文中說過了——相當(dāng)于n/3,后三行是
注意,在這里n/3不保留小數(shù)點,所以3*(n/3)不等同于n。
所以以上的公式就是我們的取余運算。

后記
寫編譯器的人果然都是數(shù)學(xué)大師,能夠想得到常人所想不到的。
最后,如果你看上述代碼有困難的話,可以參考我做的匯編語言101的視頻。
參考工具:Compiler Explorer:?https://godbolt.org/

THE END.