c语言中的取模运算符_C语言除法算法和取模运算的实现(多种算法,多种思路)...
對(duì)計(jì)算機(jī)來(lái)說(shuō),除法與求模是整數(shù)算術(shù)運(yùn)算中最復(fù)雜的運(yùn)算。相對(duì)其他運(yùn)算(如加法與減法)來(lái)說(shuō),這兩種算法的執(zhí)行速度非常慢。例如,ARM 硬件上不支持除法指令,編譯器調(diào)用 C 庫(kù)函數(shù)來(lái)實(shí)現(xiàn)除法運(yùn)算。直接利用 C 庫(kù)函數(shù)中的標(biāo)準(zhǔn)整數(shù)除法程序要花費(fèi) 20~100 個(gè)周期,消耗較多資源。
在非嵌入式領(lǐng)域,因?yàn)?CPU 運(yùn)算速度快、存儲(chǔ)器容量大,所以執(zhí)行除法運(yùn)算和求模運(yùn)算消耗的這些資源對(duì)計(jì)算機(jī)來(lái)說(shuō)不算什么。但是在嵌入式領(lǐng)域,消耗大量資源帶來(lái)的影響不言而喻。因此,從理論上講,我們應(yīng)該在程序表達(dá)式中盡量減少對(duì)除法運(yùn)算與求模運(yùn)算的使用,盡量使用其他方法來(lái)代替除法與求模運(yùn)算。例如,對(duì)于下面的示例代碼:
if (x/y>z)
{
// ...
}
我們可以將其修改成如下形式:
if (((y>0)&&(x>y*z))||((y<0)&&(x
{
// ...
}
這樣就簡(jiǎn)單地避免了一些除法運(yùn)算。同時(shí),也可以在表達(dá)式中通過(guò)合并除法的方式來(lái)減少除法運(yùn)算,下面通過(guò)示例來(lái)講解。對(duì)于如下代碼:
double x=a/b/c;
double y=a/b+c/b;
根據(jù)數(shù)學(xué)結(jié)合原則,上面的代碼可以通過(guò)合并的方式減少代碼中的除法運(yùn)算,修改后的代碼如下:
double x=a/(b*c);
double y=(a+c)/b;
同樣,對(duì)于求模運(yùn)算,也可以采用相應(yīng)的方法來(lái)代替,如下面的示例代碼:
a=a%8;
可以修改為:
a=a&7;
對(duì)于下面的表達(dá)式:
x=(x+y)%z;
可以通過(guò)如下方式來(lái)避免使用模操作符:
x+=y;
while(x>=z)
{
x-=z;
}
通過(guò)上面的闡述,相信大家對(duì)如何減少使用除法與模運(yùn)算有了初步了解。下面將詳細(xì)討論如何優(yōu)化除法運(yùn)算與求模運(yùn)算。
用倒數(shù)相乘來(lái)實(shí)現(xiàn)除法運(yùn)算
何為倒數(shù)相乘?其實(shí)很簡(jiǎn)單,它的核心思想就是利用乘法來(lái)代替實(shí)現(xiàn)除法運(yùn)算。例如,在 IA-32 處理器中,乘法指令的運(yùn)算速度比除法指令要快 4~6 倍。因此,在某些情況下盡量使用乘法指令來(lái)代替除法指令。
那么,我們?cè)撊绾卫贸朔▉?lái)代替實(shí)現(xiàn)除法運(yùn)算呢?原理就是被除數(shù)乘以除數(shù)的倒數(shù),用公式表現(xiàn)為:
x/y=x*(1/y)
例如,計(jì)算 10/5,可以根據(jù)公式 x/y=x*(1/y) 這樣來(lái)計(jì)算:
10/5=10*(1/5)=10*0.2=2
在實(shí)際應(yīng)用中,一些編譯器也正是基于這個(gè)原理才得以將除法運(yùn)算轉(zhuǎn)換為乘法運(yùn)算的。現(xiàn)在我們來(lái)看一個(gè)除法運(yùn)算示例:
#include
int main(void)
{
int x = 3/2;
float y = 3.0/2.0;
printf("3/2 = %d\r\n3.0/2.0 = %1.1f\n",x,y);
return 0;
}
運(yùn)算結(jié)果為:
3/2 = 1
3.0/2.0 = 1.5
通過(guò)該除法運(yùn)算示例可以看出,很明顯沒(méi)能充分考慮到浮點(diǎn)類(lèi)型。另外,在 C 語(yǔ)言中,一般情況下 1 除以任何數(shù)其結(jié)果皆為 0。那么怎樣才能解決這個(gè)問(wèn)題呢?編譯器采用了一種稱(chēng)為“定點(diǎn)運(yùn)算”(fixed-point arithmetic)的方法。
那么何為定點(diǎn)運(yùn)算,定點(diǎn)運(yùn)算有什么特點(diǎn)呢?
前面已經(jīng)闡述過(guò),由于計(jì)算機(jī)表示實(shí)數(shù)時(shí)為了在固定位數(shù)內(nèi)能表示盡量精確的實(shí)數(shù)值,分配給表示小數(shù)部分的位數(shù)并不是固定的,也就是說(shuō)“小數(shù)點(diǎn)是浮動(dòng)的”,因此計(jì)算機(jī)表示的實(shí)數(shù)數(shù)據(jù)類(lèi)型也稱(chēng)為浮點(diǎn)數(shù)。
相對(duì)于“小數(shù)點(diǎn)是浮動(dòng)的”來(lái)講,定點(diǎn)運(yùn)算根據(jù)字面意思來(lái)理解就是“小數(shù)點(diǎn)是固定的”。有了定點(diǎn)運(yùn)算,表示小數(shù)時(shí)不再用階碼(exponent component,即小數(shù)點(diǎn)在浮點(diǎn)數(shù)據(jù)類(lèi)型中的位置),而是要保持小數(shù)點(diǎn)的位置固定不變。這和硬件浮點(diǎn)數(shù)機(jī)制截然不同,硬件浮點(diǎn)數(shù)機(jī)制是由硬件負(fù)責(zé)向整數(shù)部分和小數(shù)部分分配可用的位數(shù)。有了這種機(jī)制,浮點(diǎn)數(shù)就可以表示很大范圍的數(shù)——從極小的數(shù)(在 0~1 的實(shí)數(shù))到極大的數(shù)(在小數(shù)點(diǎn)前有數(shù)十個(gè) 0)。這種小數(shù)的定點(diǎn)表示法有很多優(yōu)點(diǎn),尤其能極大地提高效率。當(dāng)然,作為代價(jià),同樣也必須承受隨之而來(lái)的精度上的損失。
對(duì)于定點(diǎn)數(shù)表示法(fixed-point),相信大家并不陌生。所謂定點(diǎn)格式,即約定機(jī)器中所有數(shù)據(jù)的小數(shù)點(diǎn)位置是固定不變的。在計(jì)算機(jī)中通常采用兩種簡(jiǎn)單的約定:將小數(shù)點(diǎn)的位置固定在數(shù)據(jù)的最高位之前(即定點(diǎn)小數(shù)),或者固定在最低位之后(即定點(diǎn)整數(shù))。
其中,定點(diǎn)小數(shù)是純小數(shù),約定的小數(shù)點(diǎn)位置在符號(hào)位之后、有效數(shù)值部分的最高位之前。若數(shù)據(jù) x 的形式為 x=x0x1x2…xn(其中 x0 為符號(hào)位,x1,…,xn 是數(shù)值的有效部分,也稱(chēng)為尾數(shù),x1 為最高有效位),則在計(jì)算機(jī)中的表示形式為:
一般說(shuō)來(lái),如果最末位 xn=1,前面各位都為 0,則數(shù)的絕對(duì)值最小,即 |x|min=2-n;如果各位均為 1,則數(shù)的絕對(duì)值最大,即 |x|max=1-2-n。因此定點(diǎn)小數(shù)的表示范圍是:
定點(diǎn)整數(shù)是純整數(shù),約定的小數(shù)點(diǎn)位置在有效數(shù)值部分最低位之后。若數(shù)據(jù) x 的形式為 x=x0x1x2…xn(其中 x0 為符號(hào)位,x1,…,xn 是尾數(shù),xn 為最低有效位),則在計(jì)算機(jī)中的表示形式為:
由此可知,定點(diǎn)整數(shù)的表示范圍是:
當(dāng)數(shù)據(jù)小于定點(diǎn)數(shù)能表示的最小值時(shí),計(jì)算機(jī)將它作 0 處理,稱(chēng)為下溢;當(dāng)數(shù)據(jù)大于定點(diǎn)數(shù)能表示的最大值時(shí),計(jì)算機(jī)將無(wú)法表示,稱(chēng)為上溢,上溢和下溢統(tǒng)稱(chēng)為溢出。
當(dāng)計(jì)算機(jī)采用定點(diǎn)數(shù)表示時(shí),對(duì)于既有整數(shù)又有小數(shù)的原始數(shù)據(jù),需要設(shè)定一個(gè)比例因子,數(shù)據(jù)按該比例縮小成定點(diǎn)小數(shù)或擴(kuò)大成定點(diǎn)整數(shù)再參加運(yùn)算。在運(yùn)算結(jié)果中,根據(jù)比例因子,將數(shù)據(jù)還原成實(shí)際數(shù)值。若比例因子選擇不當(dāng),往往會(huì)使運(yùn)算結(jié)果產(chǎn)生溢出或降低數(shù)據(jù)的有效精度。
使用牛頓迭代法求除數(shù)的倒數(shù)
在上一小節(jié),我們闡述了如何使用倒數(shù)相乘(x/y=x*(1/y))的方法來(lái)實(shí)現(xiàn)除法運(yùn)算。然而,對(duì)于如何能夠快速有效地取倒數(shù),牛頓迭代法(Newton’s method)是最佳方案。
對(duì)于牛頓迭代法,相信學(xué)過(guò)高等數(shù)學(xué)的讀者并不陌生,它又稱(chēng)為牛頓-拉夫遜方法(Newton-Raphson method),是牛頓在 17 世紀(jì)提出的一種在實(shí)數(shù)域和復(fù)數(shù)域上近似求解方程的方法,它將非線(xiàn)性方程線(xiàn)性化,從而得到迭代序列的一種方法。
對(duì)于方程 f(x)=0,設(shè) x0 為它的一個(gè)近似根,則函數(shù) f(x) 在 x0 附近截?cái)喔叽雾?xiàng)可用一階泰勒多項(xiàng)式展開(kāi)為如下形式:
這樣,由式(1)我們可以將 f(x)=0 轉(zhuǎn)化為如下形式:
在這里,我們?cè)O(shè) f′(x)≠0,則有:
取 x 作為原方程新的近似根 x1,再代入方程,如此反復(fù),于是就產(chǎn)生了迭代公式:
有了迭代公式(4)之后,現(xiàn)在我們繼續(xù)來(lái)看如何用牛頓迭代公式來(lái)求倒數(shù),即求除數(shù) a 的倒數(shù) 1/a。
這里我們?cè)O(shè):
式中 x 為 a 的倒數(shù),方程 f(x)=0 為一非線(xiàn)性方程。現(xiàn)在把 f(x)=0 代入牛頓迭代序列式(4)中,就可以得出求倒數(shù)的公式,如下所示:
在式(5)中,xn 為第 n 次迭代的近似根。
如式(5)所示,用牛頓迭代法求倒數(shù),每次迭代需要一次減法與兩次乘法,所用的迭代次數(shù)決定最終的計(jì)算速度和精度。迭代次數(shù)越多,則精度越高。但迭代次數(shù)越多,速度也越慢,因此實(shí)際運(yùn)用時(shí)應(yīng)綜合考慮速度和精度兩方面的因素,選擇合適的迭代次數(shù)。
其實(shí),牛頓迭代法在程序中應(yīng)用得非常廣泛,如最常用的開(kāi)方、開(kāi)方求倒數(shù)等。在 QuakeⅢ 源碼中,在 game/code/q_math.c 文件中就有一個(gè)函數(shù) Q_rsqrt,它的作用是將一個(gè)數(shù)開(kāi)平方后取倒,其運(yùn)行效率也非常高。其函數(shù)實(shí)現(xiàn)為:
float Q_rsqrt(float number)
{
long i;
float x2, y;
const float threehalfs = 1.5F;
x2 = number * 0.5F;
y = number;
i = * (long * ) &y;
i = 0x5f3759df - (i >> 1);
y = * ( float * ) &i;
// 第一次迭代
y = y * ( threehalfs - ( x2 * y * y ));
// 第二迭代
// y = y * ( threehalfs - ( x2 * y * y ) );
return y;
}
從代碼中可以看出,程序首先猜測(cè)出一個(gè)接近 1.0/sqrt(number) 的近似值,然后兩次使用牛頓迭代法進(jìn)行迭代(實(shí)際只需要使用一次)。這里需要特別注意的是 0x5f3759df 這個(gè)值,因?yàn)橥ㄟ^(guò)執(zhí)行語(yǔ)句“0x5f3759df-(i>>1)”,得出的值出人意料地接近 1/sqrt(number) 的值,因此,我們只需要一次迭代就可以求得近似解,或許這就是數(shù)學(xué)的神奇。
用減法運(yùn)算來(lái)實(shí)現(xiàn)整數(shù)除法運(yùn)算
我們知道,減法運(yùn)算比除法運(yùn)算要快得多。因此,對(duì)整數(shù)除法運(yùn)算來(lái)說(shuō),如果知道被除數(shù)是除數(shù)很小的倍數(shù),那么可以使用減法運(yùn)算來(lái)代替除法運(yùn)算。例如,對(duì)于下面的示例代碼:
unsigned int x=300;
unsigned int y=100;
unsigned int z=x/y;
我們可以將“z=x/y”表達(dá)式修改成如下形式:
unsigned int x=300;
unsigned int y=100;
unsigned int z=0;
while (x>=y)
{
x-=y;
++z;
}
這里使用減法來(lái)代替除法運(yùn)算,雖然代碼看起來(lái)不是很直觀,但是在運(yùn)行效率上確實(shí)要快許多。當(dāng)然,具體效率也要取決于被除數(shù)與除數(shù)的倍數(shù)。如果倍數(shù)比較大,那么相應(yīng)的循環(huán)次數(shù)就會(huì)增多,采取這種方法就得不償失了。
用移位運(yùn)算實(shí)現(xiàn)乘除法運(yùn)算
用移位運(yùn)算來(lái)實(shí)現(xiàn)乘除法運(yùn)算的方法,相信大家并不陌生,實(shí)際上有很多 C 編譯器都能夠自動(dòng)地做好這個(gè)優(yōu)化。通常,如果需要乘以或除以 2n,都可以用移位的方法代替。例如:
a=a*2;
b=b/2;
可以修改為如下形式:
a=a<<1;
b=b>>1;
其中,除以 2 等價(jià)于右移 1 位,乘以 2 等價(jià)于左移 1 位。同理,除以 4 等價(jià)于右移 2 位,乘以 4 等價(jià)于左移 2 位;除以 8 等價(jià)于右移 3 位,乘以 8 等價(jià)于左移 3 位,以此類(lèi)推。
其實(shí),利用上面的原理,只要是乘以或除以一個(gè)整數(shù),均可以用移位運(yùn)算的方法來(lái)得到結(jié)果,例如:
a=a*5;
可以將其分解為 a*(4+1),即 a*4+a*1。由此,我們就可以很簡(jiǎn)單地得到下面的程序表達(dá)式:
a=(a<<2)+a
盡量將浮點(diǎn)除法轉(zhuǎn)化為相應(yīng)的整數(shù)除法運(yùn)算
有時(shí)候,如果不能夠在代碼中避免除法運(yùn)算,那么盡量使除數(shù)和被除數(shù)是無(wú)符號(hào)類(lèi)型的整數(shù)。實(shí)際上,有符號(hào)的除法運(yùn)算執(zhí)行起來(lái)比無(wú)符號(hào)的除法運(yùn)算更加慢,因?yàn)橛蟹?hào)的除法運(yùn)算要先取得除數(shù)和被除數(shù)的絕對(duì)值,再調(diào)用無(wú)符號(hào)除法運(yùn)算,最后再確定結(jié)果的符號(hào)。
同時(shí),對(duì)于浮點(diǎn)除法運(yùn)算,可以先將浮點(diǎn)除法運(yùn)算轉(zhuǎn)化為相應(yīng)的整數(shù)除法運(yùn)算,最后對(duì)結(jié)果進(jìn)行相應(yīng)處理。例如,可以將浮點(diǎn)除法運(yùn)算的分子和分母同時(shí)放大相同的倍數(shù),就可以將浮點(diǎn)除法運(yùn)算轉(zhuǎn)換成相同功能的整數(shù)除法運(yùn)算。
總結(jié)
以上是生活随笔為你收集整理的c语言中的取模运算符_C语言除法算法和取模运算的实现(多种算法,多种思路)...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 在django中使用celery
- 下一篇: keil5破解失败【经验分享】