目錄:
-
前言
-
大數加法
-
大數減法
-
大數乘法
-
大數除法
-
大數階乘位數
-
大數階乘求解
-
總結
一.前言.
眾所周知,計算機數據類型的長度是有限的,因此在處理較大的數據時候會發生數據溢出,此時聰明的我們需要想辦法處理這批數據,那么我們如何處理呢?答案是用數組存儲數據,再做批量處理。
不知道你們是否觀察過用遞歸求階乘,當n為13時(數據類型為int型),數據明顯不對,有朋友可能就說了如果是longlong型數據呢?longlong型數據肯定可以求出13以上,但是100以上呢?是不是又越界了?這里我參考了其他博主的文章寫了一下大數算法的有關內容。
二.正文
一.大數加法
所謂大數加法,就是在我們計算機數據無法存儲的大數據時進行的加法運算。那我們如何實現大數加法呢,我們可以用數組,鏈表等結構來分段存儲大數據,這里我采用 數組進行大數運算的描寫。假如我們輸入123456789123456 + 123這兩個數據時,如果要發生相加的運算,那么第一個數字怎么存儲呢?答案是用數組來存,准確點說是字符數組,我們把這兩個當成一個字符串輸入到一個字符數組中,然后再進行倒序處理,為什么要倒序處理呢?比如說123456789123456 + 123,這里我們把6和3相加,5和2相加,4和3相加,但是在程序中是否表達對位相加不便呢?還有一個優點是我們可以很方便的進行進位處理,所以我們采用倒序處理。那怎么倒序呢,用整形數組從下表0開始存字符數組從后往前推的字符-‘0’(這里解釋一下減字符0是為了從字符轉成相應的數字,如果不了解可以去查找Ascii碼表)。兩個數組依次進行這個操作后,那么用一個sum數組進行相加即可。具體代碼實現如下:
#include "bits/stdc++.h"
#include "cstring"
using namespace std;
char s1[600],s2[600];
int a[600],b[600];
int sum[1200];
int main()
{
int i,j,res;
int length1,length2;
int len;
cin >> s1;
cin >> s2;
length1 = strlen(s1);
length2 = strlen(s2);
j = 0;
for(i = length1 - 1 ;i >= 0;i--)
a[j++] = s1[i] - '0';//倒轉
j = 0;
for(i = length2 - 1;i >= 0;i--)
b[j++] = s2[i] - '0';//倒轉
if(length1 > length2)
len = length1;//遍歷到最長的那個數字
else
len = length2;
res=0;//存進位
for(i = 0;i < len;i++)
{
sum[i] = (a[i] + b[i] + res) % 10;
res = (a[i] + b[i] + res)/10;
}
if(res)//如果此時還存在進位
{
sum[i] = res;
i++;
}
for(j = i - 1;j >= 0;j--)
cout << sum[j];//從后往前打印
cout << endl;
return 0;
}
二.大數減法
大數減法采用大數加法的思路存儲數據,處理數據時從低位開始往高位減,例如123456 - 123 逆轉后就是654321 - 321,這時候6 - 3,5- 2,4-1,然后其他的可以直接賦給minus數組了,如果被減數相應位數小於減數相應位數的話,我們采用借位的方式。例如1234-345,4-5<0,往前借位,借位后前一位-1,也就是3-1,(這里我默認是逆轉過后的數組)。如果被減數小於減數呢?我們怎么判斷呢?這時候我們只需要判斷如果被減數的長度小於減數的長度,那么我們可以直接輸出一個負號,然后再用減數-被減數。有人又問了,那如果兩個數位數相等,且被減數小於減數呢?此時我們只需要把逆轉后的數組從前往后遍歷,如果發現被減數位數小於減數位數的話,我就可以判斷出來了。直接輸出一個負號,然后用減數減去被減數,總的來說思路就是如果被減數大於減數,那么可以直接減去就行;如果被減數小於減數,就直接輸出一個負號,然后用減數減去被減數。例如123456 - 789 ,123456是被減數,789是減數,別搞混了啊。接下來貼出我的代碼實現(代碼有點長,有能力的小伙伴可以自行實現):
#include "bits/stdc++.h"
using namespace std;
char s1[100],s2[100];
int length1,length2;
int a1[100],b2[100];
int i,j;
int len;
int minus1[200];
int main()
{
cin >> s1 >>s2;
length1 = strlen(s1);
length2 = strlen(s2);
j=0;//從頭開始添加a1數組
for(i = length1 - 1;i >= 0;i--)
{
a1[j++] = s1[i] - '0';//倒置
}
j=0;//從頭開始添加b數組
for(i = length2 - 1;i >= 0;i--)
{
b2[j++] = s2[i] - '0';
}
len = (length1 > length2)?length1 : length2;//len為兩個字符串最長的那個
if(length1 - length2 > 0)//第一個數組減第二個
{
for(i = 0;i < len;i++)
{
if(a1[i] >= b2[i])//如果大於的話直接減
minus1[i] = a1[i] - b2[i];
else
{
minus1[i] = a1[i] + 10 -b2[i];
a1[i+1] = a1[i+1] -1;
}
}
for(j = len -1;j >= 0;j--)//因為兩個數長度不一樣,所以相減一定不為0,否則一定要保留最后一個0
if(minus1[j] == 0)
len--;//進行刪0操作
else
break;
}
else if(length1 - length2 < 0)//第二個數組減第一個
{
cout << "-";//輸出負號
for(i = 0;i < len;i++)
{
if(b2[i] >= a1[i])//如果大於的話直接減
minus1[i] = b2[i] - a1[i];
else
{
minus1[i] = b2[i] + 10 -a1[i];
b2[i+1] = b2[i+1] -1;
}
}
for(j = len -1;j >= 0;j--)//因為兩個數長度不一樣,所以相減一定不為0,否則一定要保留最后一個0列如99-100
if(minus1[j] == 0)
len--;//進行刪0操作
else
break;
}
else//如果相等則判斷誰大誰小
{
for(i = len - 1;i >= 0;i--)
{
if(a1[i] == b2[i])
continue;
else if(a1[i] > b2[i])
break;
else
break;
}
if(a1[i] > b2[i])
{
for(i = 0;i < len;i++)
{
if(a1[i] >= b2[i])//如果大於的話直接減
minus1[i] = a1[i] - b2[i];
else
{
minus1[i] = a1[i] + 10 -b2[i];
a1[i+1] = a1[i+1] -1;
}
}
for(j = len -1;j >= 0;j--)//因為兩個數長度不一樣,所以相減一定不為0,否則一定要保留最后一個0
if(minus1[j] == 0)
len--;//進行刪0操作
else
break;
}
else if(a1[i] < b2[i])
{
cout << "-";//輸出負號
for(i = 0;i < len;i++)
{
if(b2[i] >= a1[i])//如果大於的話直接減
minus1[i] = b2[i] - a1[i];
else
{
minus1[i] = b2[i] + 10 -a1[i];
b2[i+1] = b2[i+1] -1;
}
}
for(j = len -1;j >= 0;j--)//因為兩個數長度不一樣,所以相減一定不為0,否則一定要保留最后一個0列如99-100
if(minus1[j] == 0)
len--;//進行刪0操作
else
break;
}
else
{
printf("0");
return 0;
}
}
for(j = len-1;j >= 0;j--)
cout << minus1[j];
cout << endl;
return 0;
}
三.大數乘法
不知道小伙伴們是否了解過計算器的原理,我曾有幸在圖書館了解過一本有關計算器原理的書,里面就介紹了一下計算器的四則運算,所謂乘法就是多次加法,小學時候我們還不懂乘法的時候計算5*3,就會把5加上三次,其實這個就是乘法的原理——對同一個數累加。那我們如何用程序實現它呢,比如說123 * 45 我們逆轉后為321 *54 ,3*5就是15個1,3*4就是12個十, 2*5就是10個十,2*4就是8個一百,然后1*5就是五個一百,1*4就是4個1千,然后將結果進位即可。具體代碼實現如下:
#include "bits/stdc++.h"
#include "cstring"
using namespace std;
char s1[100],s2[100];
int a1[100],b2[100];
int multi[200];
int main()
{
int i,j;
cin >> s1;
cin >> s2;
j = 0;
for(i = strlen(s1) - 1;i >= 0; i--)
a1[j++] = s1[i] - '0';
j = 0;
for(i = strlen(s2) - 1; i >= 0; i--)
b2[j++] = s2[i] - '0';//翻轉數字
for(i = 0;i < strlen(s1); i++)
for(j = 0;j < strlen(s2); j++)
{
multi[i + j] = multi[i + j] + a1[i] * b2[j];//先不計算進位
}
for(i = 0;i < strlen(s1) + strlen(s2); i++)//兩數相乘最大位數為兩數位數相加
if(multi[i] >= 10)
{
multi[i + 1] += multi[i] / 10;//注意數組要提前開大一點,否則會越界
multi[i] = multi[i] % 10;
}
for(i = strlen(s1) + strlen(s2) - 1;i >= 1; i--)//刪除多余0的操作
if(multi[i] != 0)
break;
while(i >= 0)
{
cout << multi[i--];
}
cout << endl;
return 0;
}
四.大數除法
大數除法和大數乘法相反是不斷做減操作,比如說1234 / 23,那么除一次商就可以加1,當被除數小於除數時直接輸出就可以了,就可以減去53次,余下15此時就可以輸出了。但是碰到12000000 / 23呢?這樣子減未免效率也太低了吧,此時我們就可以把23補成和12000000一樣的位數23000000,然后用12000000 - 23000000,發現小了就把除數縮小一位,在判斷是否能減,但是如果當這個增位的0全部用完后,就不能再縮小了。舉個列子1230 - 456,首先456增1位0,變成4560,然后發現1230 < 4560,這樣我們就要減一位增位0,1230 - 456,至於怎樣減,前面已經在大數減法介紹過了就不在詳細介紹了。這里給出代碼給讀者思考。
#include "bits/stdc++.h"
#include "cstring"
using namespace std;
char s1[100],s2[100];
int a1[100],b1[100];
int len1,len2,len;//len計算len1和len2之間差的長度
int z[100];
int temp;
int subtraction(int length1,int length2)
{
int i;
for(i = 0;i < length2;i++)
{
if(a1[i] >= b1[i])
{
a1[i] -= b1[i];
}
else
{
a1[i] = a1[i] + 10 -b1[i];//如果這個數小於的話,則要借位進行減
a1[i + 1] -= 1;//借位之后減1
}
}
for(i = length1;i > 0 && !a1[i];i--);//消除被除數的前綴 必須為length1開始
return i + 1;//i每次都會進行判斷,所以要加1
}
int judge(int length1,int length2)
{
int i;
if(length1 < length2)
return -1;//小於返回-1
else
{
for(i = len1 - 1;i >= 0;i--)
{
if(a1[i] == b1[i])
continue;
if(a1[i] < b1[i])//小於則返回-1
return -1;
if(a1[i] > b1[i])//大於則返回1
return 1;
}
}
return 0;//如果相等就返回0
}
int main()
{
int i,j;
cin >> s1;
cin >> s2;
len1 = strlen(s1);
len2 = strlen(s2);
if(len1 < len2)
{
cout << "consult: 0";
cout << "mod : ";
puts(s2);
}
else
{
for(i = len1 - 1,j = 0;i >= 0;i--)
{
a1[j++] = s1[i] - '0'; //反轉數組 調試成功
}
for(i = len2 - 1,j = 0;i >= 0;i--)
{
b1[j++] = s2[i] - '0'; //反轉數組 調試成功
}
len = len1 - len2;
for(i = len1 -1 ;i >= 0;i--)
if(i >= len)
b1[i] = b1[i-len];
else
b1[i] = 0;
len2 = len1;//同步字符位數 調試成功
for(j = 0;j <= len;j++)//這一步主要是處理每一位上的
{
z[len - j] = 0;
while((temp = judge(len1,len2)) >= 0)
{
z[len - j]++;//如果滿足則可以加1次
len1 = subtraction(len1,len2);
cout << len1 << " " << j << endl;
cout << a1[0] << " " << a1[1] << endl;
}
if(temp < 0 && b1[0] == 0)//除數減1 如果前綴不為0的話可以結束了
{
for(i = 0;i < len2 - 1;i++)
{
b1[i] = b1[i + 1];
}
b1[len2 - 1] = 0;
len2--;//減小除數位數
}
}
}
for(i = len;i > 0;i--)
{
if(z[i])
break;//消除商的前綴0
}
cout << "consult:";
while(i >= 0)
cout << z[i--];
cout << endl;
cout << "mod:";
for(i = len1 - 1;i > 0;i--)
{
if(a1[i])
break;//消除余數的前綴0
}
if(len1 == 0)
i = len1;
while(i >= 0)
cout << a1[i--];
cout << endl;
return 0;
}
五.大數階乘位數
不知道小伙伴是否碰到過用int型求階乘時,會發現數據明顯不對,比如說求13的階乘,我們發現打印13的階乘為
然而我們可以百度搜索階乘計算器后可以得出
那么時哪里出錯了呢,答案是數據類型長度不夠。所以我們應該改變策略,用數組存儲數據,再批量處理。接下來我們先介紹兩個計算階乘位數的算法。
1.用對數函數求解階乘位數
2.用Stirling公式求解位數。再介紹如何進行階乘的計算。
1.對數函數求位數
lg1+lg2+lg3+lgn,由對數函數特性可知,lga+lgb等於lga*b,這時候我們發現真數a*b就可以替換成我們的1和2-n。但此時為什么不會越界呢,因為lg函數返回的是一個double型,所以我們用一個double型的數去存這個值就行,這個值是較小的,所以不會發生數據溢出的情況。需要注意的是我們的位數初始化為1,輸出的時候應該把位數取整后輸出。具體代碼如下:
#include "bits/stdc++.h"
#include "cmath"
using namespace std;
int main()
{
double sum = 0;
int i;
for(i = 1;i <= 200;i++)
sum += log10(i);//計算階乘的位數 利用log10的特性進行判斷
cout << (int)(sum + 1) << endl;//sum剛開始為1位數
return 0;
}
2.Stirling公式求位數
Stirling公式證明過程復雜,我只是個記公式的小菜雞,請移步大佬的blog進行思考,這里給出鏈接如下https://www.cnblogs.com/jokerjason/p/9525590.html,這里我貼出代碼實現
#include "bits/stdc++.h"
#include "cmath"
using namespace std;
const double e = 2.7182818284;//小數越多精度越高
const double Pi = 3.1415926535;
int main()
{
int res = 0;
int n;
cin >> n;
res = 0.5 * log10(2 * Pi * n) + n * log10(n/e);
cout << res + 1;
return 0;
}
六.大數階乘求解
階乘求解得思路就是用一個數組存儲,然后每次進來一個乘數,把每個數組元素都乘以這個乘數,如果發現了高位要溢出了,那么我們可以增加一位存高位,然后就是處理進位問題了,我們依然是從低位開始向高位遍歷,如果大於10的話我們就往前進位,這里我采用的是一個數組元素存6位的方式,讀者可以采用1位存法實現大數階乘。具體代碼如下:
#include "stdio.h"
int main()
{
int i = 1,high = 0,tag = 0;//tag為低位,high為高位,i為階乘值
int a[1000] = {0};//計算精確度為7000位
a[high] = 1;//把第一次運算賦初值為1
while(i <= 100)
{
for(tag = 0;tag <= high; tag++)
a[tag] *= i;
if(a[high] >= 1000000)
high += 1;
for(tag = 0;tag <= high; tag++)
{
if(a[tag] >= 1000000)
{
a[tag + 1] += a[tag] / 1000000;
a[tag] = a[tag] % 1000000;
}
}
i++;
}
tag = high;
while(high >= 0)
{
if(high == tag)
printf("%d",a[high]);
else
printf("%06d",a[high]);//這是一個輸出小技巧,左邊補0,如果是-06d的話是右邊補零
high--;
}
return 0;
}
七.總結
第一次發表博客,寫的不好的地方請及時指出,我會修改的!另外學習是一件很充實又很快樂的事情,一起加油吧!