前言
最近遇到一道求階乘的題目,原以為極其簡單,但是階乘的結果超過了范圍最大的基本數據類型的范圍,於是就着手研究大數運算(large number computing),本篇先介紹大數加法。
原理
大數運算的原理其實就是模擬人工計算(注記:再考慮是否有其他算法。注記日期:2017.3.19),人工加法計算步驟如下:
1.將兩個操作數(operand)位數對齊。
2.從最低位開始,計算兩個操作數每位的總和再加上進位數(第一位數為0)的結果,保留其個位數。
3.若上述的結果大於10,進位數置為1,否則為0。(每位數相加的結果不會超過 9 + 9 = 18 , 因此進位數只有1和0)
4.重復上述步驟2、3,直到計算完兩個操作數其中那個位數小的最高位。
知曉了步驟,就需要考慮設計儲存操作數的數據結構。首先使用列表是毋庸置疑的,而順序結構和鏈表結構其實都行,這里為里減少代碼量和說明問題,暫且使用順序結構。
綜上,給出操作數的結構體如下:
1 typedef struct operand 2 { 3 int data[100]; 4 int digit; // 記錄該數位數 5 } OPERAND;
注意,這里給出最大位數為100,下面給出的代碼中沒有考慮溢出問題,請讀者自行解決。
實現
首先,思考上述步驟1的對齊問題,可以定義一個偏移量(offset)來存儲兩個操作數的位數之差,之后遍歷的時候可以給位數少的增加偏移量來達到對齊目的。可是這種做法會增加一定的代碼量,因此不妨試着把數字倒着存放,即數組第一個元素存儲操作數的最低位(注意前述做法的前提與此相反),這樣做就省去了寫對齊的代碼。
下面給出該出加法函數,其中結果保存到第一個操作數中:
1 void plus(OPERAND* operand1,OPERAND* operand2) 2 { 3 int carry = 0; // 進位標記 4 int i = 0 , j = 0; 5 for(;i < operand1->digit || j < operand2->digit;i++ , j++) 6 { 7 int sum = operand1->data[i] + operand2->data[j] 8 + carry; // 計算該位數總和加上進位數 9 operand1->data[i] = sum % 10; // 更新該位數的數據 10 carry = (sum >= 10)? 1 : 0 ; // 判斷是否進位 11 } 12 operand1->digit = (operand1->digit > operand2->digit)? 13 operand1->digit : operand2->digit ; 14 // 更新位數 15 if(carry) // 若最后需要進位 16 { 17 operand1->data[operand1->digit] = 1; 18 operand1->digit += 1; 19 } 20 }
其中,sum % 10 用於求個位數,還有注意最后別忘了最高位的進位問題。