高精度算法 (C/C++)
作ACM題的時候,常常遇到大數的加減乘除,乘冪,階乘的計算,這時給定的數據類型每每不夠表示最後結果,這時就須要用到高精度算法。高精度算法的本質是把大數拆成若干固定長度的塊,而後對每一塊進行相應的運算。這裏以考慮4位數字爲一塊爲例,且輸入的大數均爲正整數(也能夠考慮其餘位,但要注意在每一塊進行相應運算時不能超出數據類型的數值範圍;有負整數的話讀入時判斷一下正負號在決定運算)。算法
1. 高精度加法
以3479957928375817 + 897259321544245爲例:數組
+897 |
+2593 |
+2154 |
+4245 |
= |
= |
= |
= |
4376 |
12172 |
4991 |
10062 |
進位0 |
進位1 |
進位0 |
進位1 |
4377 |
2172 |
4992 |
0062 |
C語言實現代碼以下:函數
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 200
//整數乘冪運算函數
int Pow(int a, int b)
{
int i = 0, result = 1;
for(i = 0; i < b; ++i)
{
result *= a;
}
return result;
}
//High Precision Of Addition
int main()
{
char stra[N], strb[N]; //字符串數組,以字符形式儲存兩個大數;
int i = 0, step = 4, carry = 0; //step表示塊長,carry爲進位位;
int lengtha, lengthb, maxlength, resultsize; //maxlength表示stra和strb兩者長度較大的那個;
int numa[N], numb[N],numc[N]; //依次儲存被加數,加數,和;
memset(numa, 0, sizeof(numa));
memset(numb, 0, sizeof(numb));
memset(numc, 0, sizeof(numc)); //初始化爲零;
scanf("%s%s", stra, strb);
lengtha = strlen(stra);
lengthb = strlen(strb); //計算兩個大數的長度
//字符數字轉爲四位一塊的整數數字
for(i = lengtha-1; i >= 0; --i)
{
numa[(lengtha-1-i)/step] += (stra[i]-'0')*Pow(10,(lengtha-1-i)%step);
}
for(i = lengthb-1; i >= 0; --i)
{
numb[(lengthb-1-i)/step] += (strb[i]-'0')*Pow(10,(lengthb-1-i)%step);
}
maxlength = lengtha > lengthb ? lengtha : lengthb;
//逐塊相加,並進位
for(i = 0; i <= maxlength/step; ++i)
{
numc[i] = (numa[i] + numb[i])%Pow(10, step) + carry; //計算和
carry = (numa[i] + numb[i])/Pow(10, step); //計算進位
}
//計算最後和的塊的總數
resultsize = numc[maxlength/step] > 0 ? maxlength/step : maxlength/step - 1;
printf("%d", numc[resultsize]);
for(i = resultsize-1; i >= 0; --i)
{
printf("%04d", numc[i]); //右對齊,補零輸出;
}
printf("\n");
return 0;
}
2. 高精度減法
與加法相似,不一樣的是要注意正負號和顯示位數的變化。以99999037289799 - 100004642015000爲例:
先判斷被減數和減數哪一個大,顯然這裏減數大,故輸出結果爲負數。在用大數減去小數,(若某一塊相減爲負數,則要向高位塊借位)以下表code
-99 |
-9990 |
-3728 |
-9799 |
1 |
56 |
473 |
5201 |
借位0 |
借位1 |
借位0 |
借位1 |
0 |
56 |
472 |
5201 |
C語言實現代碼以下:ip
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 200
//整數乘冪運算函數
int Pow(int a, int b)
{
int i = 0, result = 1;
for(i = 0; i < b; ++i)
{
result *= a;
}
return result;
}
//High Precision Of Subtraction
int main()
{
char stra[N], strb[N]; //字符串數組,以字符形式儲存兩個大數;
int i = 0, step = 4, borrow = 0, mark = 0; //step表示塊長,borrow爲借位位, mark爲結果符號位;
int lengtha, lengthb, maxlength, resultsize; //maxlength表示stra和strb兩者長度較大的那個;
int numa[N], numb[N],numc[N], *maxnum, *minnum; //依次儲存被減數,減數,和;
memset(stra, 0, sizeof(stra));
memset(strb, 0, sizeof(strb));
memset(numa, 0, sizeof(numa));
memset(numb, 0, sizeof(numb));
memset(numc, 0, sizeof(numc)); //初始化爲零;
scanf("%s%s", stra, strb);
lengtha = strlen(stra);
lengthb = strlen(strb); //計算兩個大數的長度
maxlength = lengtha >= lengthb ? lengtha : lengthb;
//字符數字轉爲四位一塊的整數數字
for(i = lengtha-1; i >= 0; --i)
{
numa[(lengtha-1-i)/step] += (stra[i]-'0')*Pow(10,(lengtha-1-i)%step);
}
for(i = lengthb-1; i >= 0; --i)
{
numb[(lengthb-1-i)/step] += (strb[i]-'0')*Pow(10,(lengthb-1-i)%step);
}
//找出較大的數
maxnum = numa;
minnum = numb;
mark = 1;
for(i = (maxlength-1)/step; i >= 0; --i)
{
if(numa[i] > numb[i])
{
maxnum = numa;
minnum = numb;
mark = 1;
break;
}
else if(numa[i] < numb[i])
{
maxnum = numb;
minnum = numa;
mark = -1;
break;
}
}
//逐塊相減,並借位
for(i = 0; i <= maxlength/step; ++i)
{
numc[i] = (maxnum[i] - minnum[i] + Pow(10, step) + borrow)%Pow(10,step); //計算差
borrow = (maxnum[i] - minnum[i] + Pow(10, step) + borrow)/Pow(10, step) - 1; //計算借位
}
//計算最後和的塊的總數
resultsize = maxlength/step;
while(!numc[resultsize]) --resultsize;
printf("%d", mark*numc[resultsize]);
for(i = resultsize-1; i >= 0; --i)
{
printf("%04d", numc[i]); //右對齊,補零輸出;
}
printf("\n");
return 0;
}
3. 高精度乘法
乘法能夠看做是乘數每一位與被乘數相乘後再相加,以4296556241 x 56241爲例:ci
1 |
42 |
9655 |
6241 |
4 |
168*10 |
38620*10 |
24964*10 |
2 |
84*100 |
19310*100 |
12482*100 |
6 |
252*1000 |
57930*1000 |
37446*1000 |
5 |
210*10000 |
48275*10000 |
31205*10000 |
累加和 |
2362122 |
543006855 |
351000081 |
進位(從低位向高位) |
241 |
54304 |
35100 |
C語言實現代碼以下:字符串
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 200
//整數乘冪運算函數
int Pow(int a, int b)
{
int i = 0, result = 1;
for(i = 0; i < b; ++i)
{
result *= a;
}
return result;
}
//High Precision Of Multiplication
int main()
{
char stra[N], strb[N]; //字符串數組,以字符形式儲存兩個大數;
int i = 0, j = 0, k = 0, step = 4, carry = 0; //step表示塊長,carry爲進位位;
int lengtha, lengthb, resultsize, tmpsize, eachnum; //resultsize儲存塊的總數,eachnum用來儲存乘數的每一位
int numa[N], numb[N], numc[N], tmp[N]; //依次儲存被乘數數&積,乘數;
memset(numa, 0, sizeof(numa));
memset(numb, 0, sizeof(numb));
memset(numc, 0, sizeof(numc)); //初始化爲零;
scanf("%s%s", stra, strb);
lengtha = strlen(stra);
lengthb = strlen(strb); //計算兩個大數的長度
//將被乘數字符數字轉爲四位一塊的整數數字
for(i = lengtha-1; i >= 0; --i)
{
numa[(lengtha-1-i)/step] += (stra[i]-'0')*Pow(10,(lengtha-1-i)%step);
}
//將乘數數字字符數字轉爲一位一塊的整數數字
for(i = lengthb-1; i >= 0; --i)
{
numb[lengthb-1-i] = strb[i]-'0';
}
resultsize = tmpsize = (lengtha-1)/step;
//取乘數的每一位與被乘數的逐塊相乘,並進位;
for(i = 0; i < lengthb; ++i)
{
memcpy(tmp, numa, sizeof(numa)); //將numa數組賦值給tmp數組;
k = i/step; //k儲存每一塊須要向高位塊移動的次數;
if(k)
{
for(j = tmpsize; j >= 0; --j)
{
tmp[j+k] = tmp[j];
tmp[j] = 0;
}
tmpsize += k;
}
//乘以乘數每一位擴展成的塊;
eachnum = numb[i]*Pow(10, i%step);
for(j = 0; j <= tmpsize; ++j)
{
tmp[j] *= eachnum;
}
//大數相加
carry = 0; //進位置零;
for(j = 0; j <= resultsize; ++j)
{
numc[j] += tmp[j] + carry;
carry = numc[j]/Pow(10,step);
numc[j] %= Pow(10, step);
}
if(carry)
{
++resultsize;
numc[j] += carry;
}
}
//輸出
printf("%d", numc[resultsize]);
for(i = resultsize-1; i >= 0; --i)
{
printf("%04d", numc[i]); //右對齊,補零輸出;
}
printf("\n");
return 0;
}
4. 高精度除法
高精度除法有兩種,一種是高精度除以低精度,另外一種是高精度除以高精度。前者只需將每一塊除以低精度除數便可;後者則考慮用高精度減法來實現,即每次減去高精度除數,直到減到小於除數,則減的次數即爲商,剩餘的即爲餘數。string
- 高精度除以低精度
以9876342876 / 343爲例:
C語言代碼實現以下:it
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 200
//整數乘冪運算函數
int Pow(int a, int b)
{
int i = 0, result = 1;
for(i = 0; i < b; ++i)
{
result *= a;
}
return result;
}
//High Precision Of division
//(1)高精度除以低精度
int main()
{
char stra[N]; //字符串數組,以字符形式儲存高精度被除數;
int i = 0, step = 4, carry = 0; //step表示塊長,carry爲高位向低位進位位;
int lengtha, resultsize;
int numa[N], numb, numc[N], numd; //依次儲存被除數,除數,商, 餘數;
memset(numa, 0, sizeof(numa));
memset(numc, 0, sizeof(numc)); //初始化爲零;
scanf("%s%d", stra, &numb);
lengtha = strlen(stra); //計算被除數的長度
//字符數字轉爲四位一塊的整數數字
for(i = lengtha-1; i >= 0; --i)
{
numa[(lengtha-1-i)/step] += (stra[i]-'0')*Pow(10,(lengtha-1-i)%step);
}
carry = 0; //高位向低位進位位置零
resultsize = (lengtha-1)/step;
//逐塊相除,高位向低位進位
for(i = resultsize; i >= 0; --i)
{
numc[i] = (numa[i] + carry*Pow(10,step))/numb; //計算商
carry = (numa[i] + carry*Pow(10,step))%numb; //計算進位
}
numd = carry; //最低位塊的餘數即爲整個除法的餘數
//計算最後和的塊的總數
while(!numc[resultsize]) --resultsize;
//輸出商
printf("%d", numc[resultsize]);
for(i = resultsize-1; i >= 0; --i)
{
printf("%04d", numc[i]); //右對齊,補零輸出;
}
//輸出餘數
printf("\n%d\n", numd);
return 0;
}
- 高精度除以高精度
以176342876 / 3453452爲例:
- (51 x 除數) |
51 x 3453452 |
餘數 |
216824 |
商 |
51 |
C語言代碼實現以下:io
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 200
//整數乘冪運算函數
int Pow(int a, int b)
{
int i = 0, result = 1;
for(i = 0; i < b; ++i)
{
result *= a;
}
return result;
}
//High Precision Of division
//(2)高精度除以高精度
int main()
{
char stra[N], strb[N]; //字符串數組,以字符形式儲存兩個大數;
int i = 0, step = 4, borrow = 0; //step表示塊長,borrow爲進位位;
int lengtha, lengthb, tmpnum, numbsize, numcsize, numdsize, maxsize, mark; //maxlength表示stra和strb兩者長度較大的那個;
int numa[N], numb[N], numc[N], numd[N]; //依次儲存被除數數,除數數,商,餘數;
memset(stra, 0, sizeof(stra));
memset(strb, 0, sizeof(strb));
memset(numa, 0, sizeof(numa));
memset(numb, 0, sizeof(numb));
memset(numc, 0, sizeof(numc));
memset(numd, 0, sizeof(numd)); //初始化爲零;
scanf("%s%s", stra, strb);
lengtha = strlen(stra);
lengthb = strlen(strb); //計算兩個大數的長度
//字符數字轉爲四位一塊的整數數字
for(i = lengtha-1; i >= 0; --i)
{
numa[(lengtha-1-i)/step] += (stra[i]-'0')*Pow(10,(lengtha-1-i)%step);
}
for(i = lengthb-1; i >= 0; --i)
{
numb[(lengthb-1-i)/step] += (strb[i]-'0')*Pow(10,(lengthb-1-i)%step);
}
memcpy(numd, numa, sizeof(numa));
numbsize = (lengthb-1)/step;
numcsize = 0;
numdsize = (lengtha-1)/step;
do
{
maxsize = numdsize > numbsize ? numdsize : numbsize;
//計算剩餘數是否小於除數
mark = 1;
for(i = maxsize; i >= 0; --i)
{
if(numd[i] > numb[i])
{
mark = 1;
break;
}
else if(numd[i] < numb[i])
{
mark = -1;
break;
}
}
//判斷是否餘數已經小於除數
if(!(mark+1)) break;
borrow = 0; //借位置零;
//逐塊相減,並借位
for(i = 0; i <= maxsize; ++i)
{
tmpnum = (numd[i] - numb[i] + Pow(10, step) + borrow)%Pow(10,step); //計算差
borrow = (numd[i] - numb[i] + Pow(10, step) + borrow)/Pow(10,step) - 1; //計算借位
numd[i] = tmpnum;
}
while(!numd[numdsize]) --numdsize;
//每減一個除數,商加一;
borrow = 1;
for(i = 0; i <= numcsize; ++i)
{
numc[i] += borrow;
borrow = numc[i]/Pow(10,step);
numc[i] %= Pow(10,step);
}
if(borrow)
{
++numcsize;
numc[i] += borrow;
}
}while(1);
printf("%d", numc[numcsize]);
for(i = numcsize-1; i >= 0; --i)
{
printf("%04d", numc[i]); //右對齊,補零輸出;
}
printf("\n");
printf("%d", numd[numdsize]);
for(i = numdsize-1; i >= 0; --i)
{
printf("%04d", numd[i]); //右對齊,補零輸出;
}
printf("\n");
return 0;
}