爲什麼需要高精度計算
對於 C++ 而言,最大的數據爲 long long(64b,8位),對於超過 8B 的數據,C++ 沒有對應的數據類型進行表示。所以我們需要知道高精度計算。更詳細的解釋,可以參考這個網頁https://blog.csdn.net/justidle/article/details/104414459。
高精度乘法計算原理
在讀小學時,我們做乘法都採用豎式方法,如圖 1 所示。 這樣,我們可以寫出兩個整數相減的算法。
我們就可以用 C++ 語言來模擬這個豎式減法的過程。我們可以考慮利用 C++ 的數組來存儲對應數據,假設用數組 A 存儲被減數 856 的每一位,具體來說就是 A1 存儲個位 6,A2 存儲十位 5,A3存儲百位 8;類似數組 A 的結構,使用數組 B 存儲減數 25;類似數組 A 的結構,使用數組 C 來存儲對應的乘積 21400。兩數相加的結果就如圖 2 所示。這樣理論上來說,我們就可以計算無限大的數據。如上圖 2 所示,下表表示對應的存儲方式。
數組 A | 數組 B | 數組 C | |
[0] | 6 | 5 | 0 |
[1] | 5 | 2 | 0 |
[2] | 8 | 4 | |
[3] | 1 | ||
[4] | 2 |
如上面的圖 2 ,首先計算被乘數與乘數的個位數字的乘積,把結果保存到積數組中,然後再用被除數乘以乘數的十位數字,把結果退一位加到積數組中。每加一次乘積結果就進行一次進位處理,放在一個新組數裏面。其方法與加法中的進位處理一樣。
關鍵點
1、a[i]*b[j] 應該累加到 i+j 的位置;
2、累加總結果爲: c[i+j] = a[i]*b[j]+jw+c[i+j];
3、所以每次累加完後:
1)帶入下一位累加的進位: jw = c[i+j] / 10;
2)本位實際數: c[i+j] %= 10。
高精度乘法實現
思路
1、定義存儲數組。
2、讀入數據處理。
3、從個位開始模擬豎式乘法的過程,完成整個乘法。
4、刪除前導 0 。所謂前導零,就是出現類似這樣數據 01234,這個 0 實際是不需要的。
5、輸出結果。倒序輸出減法的結果數組 C,因爲我們的個位是存儲在下標爲 0 的地方。
技術細節說明
定義存儲數組
根據題目的要求定義數組。這個部分代碼如下:
const int MAXN = 1e5+4; //根據題目的最大值。+4爲了防止A+B出現進位
char s1[MAXN] = {};//存儲字符串
char s2[MAXN] = {};//存儲字符串
int a[MAXN] = {};//存儲加數A
int b[MAXN] = {};//存儲加數B
int c[2*MAXN] = {};//存儲乘積
讀入數據並處理
這裏我們先考慮以下幾種情況,即 -3*5=-15 或者 -4*8=-32 或者 -3*(-2)=6,也就是說,需要對輸入數據的正負號進行判斷。這個部分代碼如下:
scanf("%s %s", s1, s2);//讀入字符串
//處理負數
bool flaga = false;//乘數a的符號
if ('-'==s1[0]) {
flaga = true;
strcpy(s1, &s1[1]);//刪除負號
}
bool flagb = false;//乘數b的符號
if ('-'==s2[0]) {
flagb = true;
strcpy(s2, &s2[1]);//刪除負號
}
//處理輸出的負號
if ((true==flaga && false==flagb) || (false==flaga && true==flagb)) {
printf("-");
}
//處理乘數1
int lena = strlen(s1);
for (int i=0; i<lena; i++) {
a[lena-i-1]=s1[i]-'0';
}
//處理乘數2
int lenb = strlen(s2);
for (int i=0; i<lenb; i++) {
b[lenb-i-1]=s2[i]-'0';
}
模擬豎式乘法
這個部分代碼如下:
//模擬豎式乘法
int jw;//上一輪計算進位
for (int i=0; i<lena; i++) {
jw=0;
for (int j=0; j<lenb; j++) {
//交叉乘積
c[i+j] = a[i]*b[j]+jw+c[i+j];//當前乘積+上次乘積進位+原數
jw = c[i+j]/10;//處理進位
c[i+j] %= 10;
}
c[i+lenb]=jw;//進位設置
}
刪除前導零
這個部分代碼如下:
//刪除前導零
int lenc=lena+lenb;
for (int i=lenc-1; i>=0; i--) {
//因爲我們是從索引 0 開始,所以最高位是保存在 len-1
if (0==c[i] && lenc>1) {
//注意要有 lenc>1 這個條件。考慮特殊情況,加法結果爲 00,我們實際要輸出 0。
lenc--;
} else {
//第一個不是零的最高位,結束刪除
break;
}
}
輸出計算結果
採用倒序的方式輸出,因爲我們數據保存是倒序結構,也就是低位在前。
//逆序打印輸出
for (int i=lenc-1; i>=0; i--) {
printf("%d", c[i]);
}
printf("\n");
例題和 AC 代碼
題目
題目鏈接
一本通 OJ:http://ybt.ssoier.cn:8088/problem_show.php?pid=1174,或者http://ybt.ssoier.cn:8088/problem_show.php?pid=1307。
我自己 OJ:http://47.110.135.197/problem.php?id=1034。
題目描述
求兩個不超過 200 位的非負整數的積。
輸入
有兩行,每行是一個不超過 200 位的非負整數,沒有多餘的前導 0。
輸出
一行,即相乘後的結果。結果裏不能有多餘的前導 0,即如果結果是 342,那麼就不能輸出爲 0342。
樣例輸入
12345678900
98765432100
樣例輸出
1219326311126352690000
分析
題目告訴我們不超過 200 位,也就是 MAXN = 200+4。特別的地方要注意乘積的長度爲 2*MAXN。
AC 代碼
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200+4; //根據題目的最大值。+4爲了防止A+B出現進位
char s1[MAXN] = {};//存儲字符串
char s2[MAXN] = {};//存儲字符串
int a[MAXN] = {};//存儲加數A
int b[MAXN] = {};//存儲加數B
int c[2*MAXN] = {};//存儲和B
int main() {
scanf("%s %s", s1, s2);//讀入字符串
//處理負數
bool flaga = false;//乘數a的符號
if ('-'==s1[0]) {
flaga = true;
strcpy(s1, &s1[1]);//刪除負號
}
bool flagb = false;//乘數b的符號
if ('-'==s2[0]) {
flagb = true;
strcpy(s2, &s2[1]);//刪除負號
}
//處理輸出的負號
if ((true==flaga && false==flagb) || (false==flaga && true==flagb)) {
printf("-");
}
//處理乘數1
int lena = strlen(s1);
for (int i=0; i<lena; i++) {
a[lena-i-1]=s1[i]-'0';
}
//處理乘數2
int lenb = strlen(s2);
for (int i=0; i<lenb; i++) {
b[lenb-i-1]=s2[i]-'0';
}
//模擬豎式乘法
int jw;//上一輪計算進位
for (int i=0; i<lena; i++) {
jw=0;
for (int j=0; j<lenb; j++) {
//交叉乘積
c[i+j] = a[i]*b[j]+jw+c[i+j];//當前乘積+上次乘積進位+原數
jw = c[i+j]/10;//處理進位
c[i+j] %= 10;
}
c[i+lenb]=jw;//進位設置
}
//刪除前導零
int lenc=lena+lenb;
for (int i=lenc-1; i>=0; i--) {
//因爲我們是從索引 0 開始,所以最高位是保存在 len-1
if (0==c[i] && lenc>1) {
//注意要有 lenc>1 這個條件。考慮特殊情況,加法結果爲 00,我們實際要輸出 0。
lenc--;
} else {
//第一個不是零的最高位,結束刪除
break;
}
}
//逆序打印輸出
for (int i=lenc-1; i>=0; i--) {
printf("%d", c[i]);
}
printf("\n");
return 0;
}