高精度演算法(C/C++)
阿新 • • 發佈:2020-01-28
高精度演算法 (C/C++)
做ACM題的時候,經常遇到大數的加減乘除,乘冪,階乘的計算,這時給定的資料型別往往不夠表示最後結果,這時就需要用到高精度演算法。高精度演算法的本質是把大數拆成若干固定長度的塊,然後對每一塊進行相應的運算。這裡以考慮4位數字為一塊為例,且輸入的大數均為正整數(也可以考慮其他位,但要注意在每一塊進行相應運算時不能超出資料型別的數值範圍;有負整數的話讀入時判斷一下正負號在決定運算)。
1. 高精度加法
以3479957928375817 + 897259321544245為例:
3479 | 9579 | 2837 | 5817 |
---|---|---|---|
+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為例:
先判斷被減數和減數哪個大,顯然這裡減數大,故輸出結果為負數。在用大數減去小數,(若某一塊相減為負數,則要向高位塊借位)如下表
100 | 0046 | 4201 | 5000 |
---|---|---|---|
-99 | -9990 | -3728 | -9799 |
1 | 56 | 473 | 5201 |
借位0 | 借位1 | 借位0 | 借位1 |
0 | 56 | 472 | 5201 |
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 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為例:
被乘數 | 42 | 9655 | 6241 |
---|
乘數 | 5 | 6 | 2 | 4 | 1 |
---|
被乘數x乘數 | 42 | 9655 | 6241 |
---|---|---|---|
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 |
積 | 241 | 6426 | 1955 | 0081 |
---|
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. 高精度除法
高精度除法有兩種,一種是高精度除以低精度,另一種是高精度除以高精度。前者只需將每一塊除以低精度除數即可;後者則考慮用高精度減法來實現,即每次減去高精度除數,直到減到小於除數,則減的次數即為商,剩餘的即為餘數。
- 高精度除以低精度
以9876342876 / 343為例:
被除數 | 98 | 7634 | 2876 |
---|
除數 | 343 |
---|
向低位塊進位 | 98 | 137 | 190 |
---|---|---|---|
商 | 0 | 2879 | 4002 |
餘數 | 190 |
---|
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 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為例:
被除數 | 176342876 |
---|---|
- (51 x 除數) | 51 x 3453452 |
餘數 | 216824 |
商 | 51 |
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 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;
}