高精度運算(加法,減法,乘法)

寫在最前面:

最近在洛谷上寫題的時候,看到一個算1000!的題目,但是C語言和C++算到13的階乘,就存放不下了,所以就根據答案,寫了一個算大數階乘的程序,與此同時,受到啓發,就又寫了寫大數的加法減法和乘法,對於除法,我現在唯一想到的辦法是進行多次減法,所以等以後有了好的想法再補上!

什麼是高精度?

對於C /C++,它們的數據類型有限,int最大爲2^31 -1, 大約二十億,long long最多是2^63-1,十進制也就二十位左右。
但是,如果一個數超過了20位,甚至一百位,兩百位,那麼這些基本的數據類型就不足以進行計算了。因此,我們就需要用到高精度的運算,說白了就是用數組來存放大數,然後模擬列豎式的過程就行。接下來依次說明

基本原則

1.按正常字符串的方式讀入數字,讀入之後都減去’0’變成數字
2.運算和進位分開做,先按位進行運算,之後統一處理進位,這樣不會搞混
3.運算的時候從最後一位開始,把結果寫進另一個數組裏,這裏指的是正序寫入另一個數組,這就需要在輸出結果時逆序輸出了
No Picture You Say a …
在這裏插入圖片描述

1.加法

加法比較容易,直接按位相加就行。加法函數裏,爲了避免分類討論兩個加數位數的問題,所以把兩個加數都加到了同一個數組裏。這是最重要的部分
這裏主要是來寫一下整體的代碼,後面乘法和減法裏相似的部分就不再說明了

#include <stdio.h>
#include <string.h>

void toNum(char s[]);//轉換位數字
int add(char a[], int len1, char b[], int len2, int result[]);//按位相加
int carry(int result[], int len);//進位

int main()
{
	char a[400];
	char b[400];
	int result[410]={0};
	
	//buffer
	
	scanf("%s", a);
	scanf("%s", b);
	int len1 = strlen(a);
	int len2 = strlen(b);
	
	toNum(a);
	toNum(b);
	
	int len3 = add(a,len1, b, len2, result);
	int len4 = carry(result, len3);
	
	int i=0;
	
	for (i=len4-1; i>=0; i--)
	{
		printf("%d", result[i]);
	}
	printf("\n");
	return 0;
}

void toNum(char s[])
{
	int i;
	int len = strlen(s);
	
	
	for (i=0; i<len; i++)
	{
		s[i] -= '0';
	}
}

int add(char a[], int len1, char b[], int len2, int result[])
{
	int i=len1-1, j=len2-1, k1=0, k2=0;
	while (i>=0)
	{
		result[k1] += a[i];
		i--;
		k1 ++;
	}
	//先把第一個數加到result數組裏
	while (j>=0)
	{
		result[k2] += b[j];
		j--;
		k2 ++;
	}
	//再把第二個數也加到result數組裏
	return (k1>k2?k1:k2);//返回大的位數,實際操作了多少位
}
int carry(int result[], int len)
{
	int i=0;
	
	for (i=0; i<len; i++)
	{
		result[i+1] += result[i]/10;//一定要先進位再取餘
		result[i] %= 10;
	}
	
	if (result[len]==0)//如果最高位是0,就說明沒進位
		return len;
	else
		return len+1;
}

接下來的代碼都是由三個部分構成,轉換爲數字(toNum),運算(add,sub,multi)
,進位(carry)等,主要說明的是運算函數

2.減法

減法和加法大同小異,首先是按位相減,然後是進位變成了借位,借位就是如果這一位小於0,就讓前一位-1,這一位加10
No Picture You Say a…
在這裏插入圖片描述大致思路就是這樣,下面是代碼

int sub(char a[], int len1, char b[], int len2, int result[])
{
	int i=len1-1, j=len2-1, k1=0, k2=0;
	while (i>=0)
	{
		result[k1] += a[i];//也不是非得用+=,直接賦值就行
		i--;
		k1 ++;
	}
	//把第一個數賦值給result,這樣才能減
	while (j>=0)
	{
		result[k2] -= b[j];
		j--;
		k2 ++;
	}
	return k1;
}

int carry(int result[], int len)//這是借位的處理
{
	int i=0;

	for (i=0; i<len; i++)
	{
		if (result[i]<0)//小於0時才能說明借位了
		{				//然後加10減1就行
			result[i] += 10; 
			result[i+1]--;
		}
	}

	while(result[len-1]==0&&len>1)//把前導0都去了,才能得到位數
	{
		len--;
	}
	return len;
}

這裏需要說明的就是:減法函數裏默認了第一個數是大於第二個數的,這樣也就更簡單了,實際上,爲了達到這個效果,前面可以再加一個緩衝區,專門用來調換順序,控制正負號。減法就說到這裏

3.乘法

乘法最重要的是乘法函數的設計,與加減法不同,乘法需要一個數每位都與另一個數相乘再相加,加的時候要對齊,比如第二位進行相乘,就從第二位開始往後加。其實,這樣也還沒解釋清楚,先從低精度乘高精度說吧
低精度就是說int能表示的數,比如9,100這些,相乘的時候讓這些數和高精度的每一位都相乘,按位記錄下來,乘完之後再考慮進位,
No Picture You Say a…
在這裏插入圖片描述這和實際的程序還是有點出入的,主要是順序不一樣,這個圖爲了說明實際的原理也就沒考慮那麼多的細節了,應該很清楚了吧
接下來解釋高精度乘以高精度
這是對前面的一個擴展,需要把每一位都當作低精度來和另一個數相乘,然後再把每一位的積相加,這裏關鍵在於相加時候的對齊問題,考慮到每次前進一位就相當於乘以了10,所以在相加的時候也要先乘10,反映到數組裏就是往前走一位對齊。也就是說,這是第幾位,就要把積從第幾位開始對齊相加
No Picture You Say a …
在這裏插入圖片描述後面就是求和,化的圖比較醜,就不再畫了(逃)

終於到了代碼時間,上代碼

int multi(char a[], int len1, char b[], int len2, int result[])
{
	if (a[0]==0||b[0]==0)
	{
		return 1;
	}//如果輸入兩個0的話,就不需要乘了
	
	int i, j, k=0;
	
	for (i=len1-1; i>=0; i--)
	{
		k = len1-1-i;//這是負責對齊的
		for (j=len2-1; j>=0; j--)
		{
			result[k] += a[i]*b[j];
			k++;
		}
	}
	
	return k;
}

int carry(int num[], int len)
{
	int i;

	for (i=0; i<len; i++)
	{
		num[i+1] += num[i]/10;
		num[i] %= 10;
	}
	
	while (num[i])
	{
		num[i+1] += num[i]/10;
		num[i] %= 10;
		i++;
	}//這是爲了把每個單元都變成只有一個數字,方便後續使用
	
	return i;
}

至於除法,還沒有想到高級的模擬方法,等以後想到了再補充上。

最後,這些其實都不算完美,比如沒有對正負號進行判斷,只是用於整數等,這些不足都還有待改進,總之,不斷地學習新知識,不斷總結積累,纔能有更完善的思路,更優質的解題過程。這次的總結,就先到這裏吧!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章