階乘之計算從入門到精通 近似計算之一

2021-04-12 21:38:51 字數 3222 閱讀 1830

<階乘之計算從入門到精通-菜鳥篇>中提到,使用double型數來計算階乘,當n>170,計算結果就超過double數的最大範圍而發生了溢位,故當n>170時,就不能用這個方法來計算階乘了,果真如此嗎?no,只要肯動腦筋,辦法總是有的。

通過windows計算器,我們知道,171!=1.2410180702176678234248405241031e+309,雖然這個數不能直接用double型的數來表示,但我們可以用別的方法來表示。通過觀察這個數,我們發現,這個數的表示法為科學計算法,它用兩部分組成,一是尾數部分1.2410180702176678234248405241031,另乙個指數部分309。不妨我們用兩個數來表示這個超大的數,用double型的數來表示尾數部分,用乙個long型的數來表示指數部分。這會涉及兩個問題:其一是輸出,這好說,在輸出時將這兩個部分合起來就可以了。另乙個就是計算部分了,這是難點所在(其實也不難)。下面我們分析一下,用什麼方法可以保證不會溢位呢?

我們考慮170!,這個數約等於7.257415e+306,可以用double型來表示,但當這個數乘以171就溢位了。我們看看這個等式:

7.257415e+306

=7.257415e+306 * 10^0 (注1)(如用兩個數來表示,則尾數部分7.257415e+306,指數部分0)

=(7.257415e+306 / 10^300 )* (10^0*10^300)

=(7.257415e6)*(10 ^ 300) (如用兩個數來表示,則尾數部分7.257415e+6,指數部分300)

依照類似的方法,在計算過程中,當尾數很大時,我們可以重新調整尾數和指數,縮小尾數,同時相應地增大指數,使其表示的數的大小不變。這樣由於尾數很小,再乘以乙個數就不會溢位了,下面給出完整的**。

程式3.

#include "stdafx.h"

#include "math.h"

#define max_n 10000000.00 //能夠計算的最大的n值,如果你想計算更大的數對數,可將其改為更大的值

#define max_mantissa (1e308/max_n) //最大尾數

struct bignum ;

void calcfac(struct bignum *p,int n)

p->n1 *=(double)i;

}} void printfresult(struct bignum *p,char buff)

sprintf(buff,"%.14fe%d",p->n1,p->n2);}

int main(int argc, char* argv)

以上**中的數的表示方式為:數的值等於尾數乘以 10 ^ 指數部分,當然我們也可以表示為:尾數 乘以 2 ^ 指數部分,這們將會帶來這樣的好處,在調整尾數部分和指數部分時,不用除法,可以依據浮點數的格式直讀取階碼和修改階碼(上文提到的指數部分的標準稱呼),同時也可在一定程式上減少誤差。為了更好的理解下面的**,有必要了解一下浮點數的格式。浮點數主要分為32bit單精度和64bit雙精度兩種。本方只討論64bit雙精度(double型)浮點數的格式,乙個double型浮點數包括8個位元組(64bit),我們把最低位記作bit0,最高位記作bit63,則乙個浮點數各個部分定義為:第一部分尾數:bit0至bit51,共計52bit,第二部分階碼:bit52-bit62,共計11bit,第三部分符號位:bit63,0:表示正數,1表示負數。如乙個數為0.***x * 2^ exp,則exp表示指數部分,範圍為-1023到

1024,實際儲存時採用移碼的表示法,即將exp的值加上0x3ff,使其變為乙個0到2047範圍內的乙個值。函式getexpbase2 中各語句含義如下:1.

「(*pword & 0x7fff)」,得到乙個

bit48-bit63這個16bit數,最高位清0。2.「>>4」,將其右移4位以清除最低位的4bit尾數,變成乙個11bit的數(最高位5位為零)。3.「

(rank-0x3ff)」

, 減去0x3ff還原成真實的指數部分。以下為完整的**。

程式4:

#include "stdafx.h"

#include "math.h"

#define max_n 10000000.00 //能夠計算的最大的n值,如果你想計算更大的數對數,可將其改為更大的值

#define max_mantissa (1e308/max_n) //最大尾數

typedef unsigned short word;

struct bignum ;

short getexpbase2(double a) // 獲得 a 的階碼

double getmantissa(double a) // 獲得 a 的 尾數

void calcfac(struct bignum *p,int n)

p->n1 *=(double)i;}}

void printfresult(struct bignum *p,char buff)

int main(int argc, char* argv)

程式優化的威力

程式 4是乙個很好的程式,它可以計算到1千萬(當n更大時,p->n2可能溢位)的階乘,但是從執行速度上講,它仍有潛力可挖,在採用了兩種優化技術後,(見程式5),速度竟提高5倍,甚至超出筆者的預計。

第一種優化技術,將頻繁呼叫的函式定義成

inline函式,inline是c++關鍵字,如果使用純c的編譯器,可採用macro來代替。

第二種優化技術,將迴圈體內的**展開,由乙個迴圈步只做一次乘法,改為乙個迴圈步做

32次乘法。

實際速度:計算

10000000!,程式4需要0.11667秒,程式5只需要0.02145秒。測試環境為迅馳1.7g,256m ram。關於程式優化方面的內容,將在後續文章專門講述。下面是被優化後的部分**,其餘**不變。

程式 5的部分**:

inline short getexpbase2(double a) // 獲得 a 的階碼

inline double getmantissa(double a) // 獲得 a 的 尾數

void calcfac(struct bignum *p,int n)

for (;i<=n;i++)

}

注1:10^0,表示10的0次方

[email protected]

階乘之計算從入門到精通 大數的表示

階乘之計算從入門到精通 大數的表示 1.大數,這裡提到的大數指有效數字非常多的數,它可能包含少則幾 十 幾百位十進位制數,多則幾百萬或者更多位十進位制數。有效數字這麼多的數隻具有數學意義,在現實生活中,並不需要這麼高的精度,比如銀河系的直徑有10萬光年,如果用原子核的直徑來度量,31位十進位制數就可...

階乘之計算從入門到精通 大數的表示

1.大數,這裡提到的大數指有效數字非常多的數,它可能包含少則幾 十 幾百位十進位制數,多則幾百萬或者更多位十進位制數。有效數字這麼多的數隻具有數學意義,在現實生活中,並不需要這麼高的精度,比如銀河系的直徑有10萬光年,如果用原子核的直徑來度量,31位十進位制數就可使得誤差不超過乙個原子核。2.大數的...

近似計算(演算法競賽入門)

看演算法書,偶然看到這個,覺得當初學校上課時候做過,當初覺得很難,現在也沒一下就能想出解法,還是需要多加練習,放假惰性太強,害,慢慢打卡!重複計算,因此可以用迴圈實現。但不同的是,只有算完一項之後才知道它是否小於10 6。也就是說,迴圈終止判斷是在計算之後,而不是計算之前。這樣的情況很適合使用do ...