C++ 的高精度乘法

爲什麼需要高精度計算

對於 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 ,首先計算被乘數與乘數的個位數字的乘積,把結果保存到積數組中,然後再用被除數乘以乘數的十位數字,把結果退一位加到積數組中。每加一次乘積結果就進行一次進位處理,放在一個新組數裏面。其方法與加法中的進位處理一樣。

C_{0}=C_{0}^{`} = A_{0}\ast B_{0}\\ C_{1}=C_{1}^{`}+C_{1}^{``} = A_{0}\ast B_{1}+A_{1}\ast B_{0}+JW_{next round}\\ C_{2}=C_{2}^{`}+C_{2}^{``} = A_{2}\ast B_{0}+A_{1}\ast B_{1}+JW_{next round}\\ C_{3}=C_{3}^{`}+C_{3}^{``} = A_{3}\ast B_{0}+A_{2}\ast B_{1}+JW_{next round}\\ \cdots \cdots

關鍵點

1a[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;
}
發佈了167 篇原創文章 · 獲贊 11 · 訪問量 104萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章