1. 程式人生 > >C語言大數運算-乘除法篇

C語言大數運算-乘除法篇

前言:
這是第三篇部落格,也是一次介紹二個計算的部落格,可能難度會比前兩篇部落格大一點,所以建議對於初學者來說一定要看完我的前兩篇部落格再來看本篇部落格,關於本次實驗的環境,和思想在第一篇部落格已經簡單介紹過了,所以不再贅述,我會先介紹大數的乘法載介紹大數的除法,乘法的難點在於要使用一個巢狀迴圈,除法的難點在於一個字使用符串比較方法的技巧,本次還是會將演算法都寫成函式,然後在main()函式中呼叫,原因是在第四篇我們要將整個大數運算的方法做成自己的一個庫檔案,可以供自己或他人使用。

大數乘法:
由於乘法可以互換所以對於輸入的數字沒有限制條件,計算方法還是模仿手工演算法,由被乘數的低位開始和乘數的每一位相乘並且要將大於9的十位數向前進一位,存在3個問題需要我們解決。

問題:
1 我們要用多大的陣列儲存結果?
2 要使用巢狀迴圈嗎?
3 如何在計算的過程中保證進位?

其實問題也很好解決,前兩個問題都可以看出答案,最後一個問題和前兩篇部落格的進位問題很相似,所以簡單的說明後再看註釋的程式碼是很好懂的。
1 二個數相乘最大的位數是兩個乘數的位數之和。
2 很明顯由於乘法的特性使用巢狀迴圈很合適。
3 在大數加減中執行完畢後再對儲存結果的result陣列進行一次進位,但在乘法中我們需要每執行一趟就要對陣列進行進位的處理。

實現:
下面是全面的含有註釋的程式碼。

  1 //#include"big.h"
  2 #include<stdio.h>
3 #include<stdlib.h> 4 #include<string.h> 5 char * bigmul(char *m,int lena,char *f,int lenb){ //乘法運算函式。 6 int i,j,k,lensum,tmp_result,carry,num='0'; 7 lensum=lena+lenb; //確定結果陣列的長度。 8 for(i=0;i<lena;i++){ //將ASCII碼轉為對應的數字儲存。
9 m[i]=m[i]-num; 10 } 11 for(i=0;i<lenb;i++){ 12 f[i]=f[i]-num; 13 } 14 char *result,final[BUFSIZ]; 15 result=(char*)calloc(lensum,1); 16 for(i=0;i<lenb;i++){ //為被乘數作一趟乘法。 17 for(j=0;j<lena;j++){ 18 tmp_result=f[lenb-i-1]*m[lena-j-1]; 19 result[j+i]+=tmp_result; 20 } 21 for(k=0;k<=j+i-1;k++){ //每作一趟乘法整理一次結果陣列。 22 if(result[k]>9){ 23 carry=result[k]/10; 24 result[k]=result[k]%10; 25 result[k+1] += carry; 26 } 27 } 28 } 29 j=0; 30 if(result[lensum-1]!=0){ //去除前導零將結果整理到final陣列中。 31 final[j]=result[lensum-1]+num; 32 j++; 33 } 34 for(i=lensum-2;i>=0;i--){ 35 final[j++]=result[i]+num; 36 } 37 result=final; //將指標指向final陣列並返回該指標。 38 return result; 39 } 40 int main(){ //利用main測試方法,用puts列印結果。 41 int lena,lenb; 42 char *result,sa[BUFSIZ],sb[BUFSIZ]; 43 scanf("%s",sa); 44 scanf("%s",sb); 45 lena=strlen(sa); 46 lenb=strlen(sb); 47 result=bigmul(sa,lena,sb,lenb); 48 puts(result); 49 50 }

下面是大數除法。

前言:
大數除法的難點在於思考演算法,可以用連續的減法來實現,舉個簡單了例子:32/2可以用32連續減去2每減一次i加一,當差小於被減數時停止。i即為商,由於我們前面實現了大數減法所以用該方法可以實現,但是有一個問題就是如果用一億除以一那麼就需要執行一億次,況且我們做的是大數數算,輸入100位以上的數也都是有可能的,那麼計算的時間就是幾天,幾年,幾萬年都有可能。所以只有模仿手工的方法,從高位開始計算。32/2從高位先用3-2只能減1次,將餘數保留變成12/2,可以減6次,從而得到結果16極大的降低了迴圈減的次數。

大數除法:
有很多問題大多都是的我們前面遇到的問題,例如結果陣列的位數,對陣列的整理進位問題,巢狀迴圈和乘法相同按趟執行,既然是相似的問題我就不再說了。

注意:
除法對資料有限制不能分母為零,分母為零沒有意義,不能用小數除以大數,因為小數除以大數本質還是大數除以小數結果加個分之一就可以了。
返回的結果是儲存商的陣列的指標,不包含餘數。

實現:
下面是完整的含有註釋的程式碼,如果想判斷輸入,或輸入餘數,可自行修改程式碼。

  1 //#include"big.h"
  2 #include<stdio.h>
  3 #include<string.h>
  4 char diva[BUFSIZ],divb[BUFSIZ];
  5 char result_tmp[BUFSIZ];
  6 char final[BUFSIZ];
  7 char * bigdiv(char *diva,int lena,char *divb,int lenb){        //大數除法函式。
  8     int i,j,k;
  9     char  * result;
 10 /*          if((lena<lenb||lena==lenb)&&strcmp(diva,divb)<0){  //去除了以小除大的判斷
 11                 printf("0 餘數=");//求餘數  
 12                 for(i=0; i<lena; i++)  
 13                     printf("%d",diva[i]-'0');  
 14                 printf("\n");  
 15                 return result;
 16             }  
 17 */
 18             k=0;
 19             while(1){                        //死迴圈只有當lena和lenb相等時跳出迴圈,因為會不斷的在divb陣列前加0所以該陣列的長度,
 20                                              //會不斷的變化當兩者相等時說明已經無法在作減法。
 21                 result_tmp[k]=0;
 22                 while(strcmp(diva,divb)>=0){ //用字串比較的方法是一個亮點,很巧妙。因為strcmp()比較的方式是從前到後依次比較 
 23                  int i=0,j;                  //如果相等則向後移動一位一旦發現不等則立即返回忽略後面的所有資料。
 24                      while(1){
 25                         if(diva[i]=='0') i++;//去除diva高位前面的0
 26                         else{
 27                            j=i;              //去除divb高位填充的0
 28                          break;  
 29                         }  
 30                }
 31     
 32                for(; i<lenb; i++)            //作減法
 33                diva[i]=diva[i]-divb[i]+'0';
 34                for(i=lenb-1; i>j; i--)       //每作一組減法就整理陣列,這種整理陣列在前幾篇中都有使用。
 35                if(diva[i]<'0'){              //不過在這裡不是整理結果陣列而是diva陣列,結果儲存在a陣列中不用整理
 36                   diva[i]+=10;;
 37                   diva[i-1]--;
 38                }
 39                     result_tmp[k]++;
 40 
 41                }
 42                   k++;
 43                   if(lena==lenb)   break;
 44                   for(i=lenb-1; i>=0; i--)   //將divb中的元素先後移位,同時擴大divb長度並且在divb前端補一位0。
 45                     divb[i+1]=divb[i];
 46                   divb[0]='0';               //由於陣列後移所以divb[0]每次移動後都為空,所以每次用0補齊。
 47                   lenb++;
 48                   divb[lenb]='\0';           //在結尾加上字串的結束標記。
 49             }
 50             i=0;j=0;
 51             while(result_tmp[i]==0) i++;
 52             for(; i<k; i++){
 53                 final[j++]=result_tmp[i]+'0';
 54 
 55             }
 56             result=final;
 57 /* 
 58             printf(" 餘數=");  
 59             j=0;//求餘數  
 60             while(diva[j]=='0')  j++;  
 61             if(j==lena)  
 62             {  
 63                 printf("0\n");  
 64                 continue;  
 65             }  
 66             for(; j<n; j++)  
 67                 printf("%d",diva[j]-'0');  
 68             printf("\n");  
 69       */
 70 
 71             return result;
 72 
 73 }
 74 int main(){                                                 //利用main測試方法,用puts列印結果。               
 75    int lena,lenb;
 76    char *result,sa[BUFSIZ],sb[BUFSIZ];
 77    scanf("%s",sa);
 78    scanf("%s",sb);
 79    lena=strlen(sa);
 80    lenb=strlen(sb);
 81    result=bigdiv(sa,lena,sb,lenb);
 82    puts(result);
 83 
 84 }

所有的運算到此完結最後一篇做一個大數運算庫,有時間我會把庫放到github如果以後有時間我會做些優化並且加入對浮點的支援。