高精度四件套(加減乘除)

江上舟搖發表於2022-02-10

唉,和高精度打交道真不是一件簡單事,尤其是高高除,但是在經過參考各位大佬,巨佬,大神,神仙,大牛,大犇資料的時候,我發現自己對於他們的思路以及程式碼清晰度的辨識還不是很高,所以下定決心寫一篇高精度的專題,以便自己日後用來複習高精度,並且根據我的思路寫出我的高精度程式碼;

首先我們先來認識一下,為什麼要用高精度:簡單而言,對於一些非常大的數字,用普通的long甚至是longlong都已經處理不了了,比如處理10^1000,這個數肯定不能用普通變數來儲存了,我們這時候就需要手寫高精度運算

基本整數型別的取值範圍

型別數值範圍佔位元組數
char -128 .. 127 1
unsigned char 0 .. 255 1
int (long) -2147483648 .. 2147483647 4
unsigned int (long) 0 .. 4294967295 4
long long -9223372036854775808 .. 9223372036854775807 8
unsigned long long 0 .. 18446744073709551615 8

如何儲存高精度數字呢,我們通常採取以輸入字元或者是字元陣列之後通過整形陣列來儲存高精度數字。
 
一、高精度加法
洛谷題目連結:https://www.luogu.com.cn/problem/P1601
拿到題我們其實很稀奇的,咦,測試樣例這麼這麼簡單嗎?直接寫printf("%d",a+b);不就行了嗎?哈哈哈,這就是“稻花香裡說豐年”——聽取WA聲一片。要關注一下資料的取值範圍a,b≤10^500,這是個什麼概念?去百度搜尋一下10的100次方就知道了。
那麼該怎麼才能AC呢?這時候就要用高精度了,首先我們先定義合適的資料範圍,比如它給的是10^500,我們就可以定義兩個數量為510(當然更大也沒關係)的char陣列,用來輸入數字用,在定義兩個int型別的陣列,用來儲存資料。
然後在模擬一下加法運算,是的,高精度運算也是一種模擬運算,比如:

模擬完成之後,我們得到的結果或許帶著前導0,所謂前導0就是0123,我們需要輸出123,這個0通過條件判斷刪去就可以了。

總結:

1、定義儲存陣列。

2、讀入資料到陣列中。注意是倒序存放,也就是個位放在陣列下標為 0 的地方。

3、從個位開始模擬豎式加法的過程,完成整個加法。

4、刪除前導0。

5、輸出加法的結果。

程式碼如下:

#include<bits/stdc++.h>
using namespace std;
char a[510],b[510];//用來輸入大數 
int c[510],d[510];//用來儲存資料 
int main()
{
    scanf("%s %s",a,b);
    int len1=strlen(a);
    int len2=strlen(b);
    for(int i=0;i<len1;i++)
    {
        c[i]=a[len1-1-i]-'0';//逆序儲存,可以這麼理解,比如存1234,由於需要十進位制進位處理,所以c[0]到c[3]分別是4321 
    }
    for(int i=0;i<len2;i++)
    {
        d[i]=b[len2-1-i]-'0';//同理 
    } 
    int len=max(len1,len2)+1;//防止最後一位出現進位的情況,所以預前多加一位備用 
    int jw=0;//進位 
    int sum[510];
    memset(sum,0,sizeof(sum));
    for(int i=0;i<len;i++)
    {
        sum[i]=c[i]+d[i]+jw;
        jw=sum[i]/10;
        sum[i]=sum[i]%10;
    }
    while(sum[len-1]==0&&len>1)//用來刪除前導0,比如我們得出的結果是0123,不需要0,刪去就好了,同下面註釋的程式碼 
    len--;
    /*for (int i=len-1; i>=0; i--) {
        if (0==sum[i] && len>1) {
            len--;
        } else {
            break;
        }
    }*/
    for(int i=len-1;i>=0;i--)
    printf("%d",sum[i]);
    return 0;
}

二、高精度減法

題目地址:https://www.luogu.com.cn/problem/P2142

對於高精度減法的儲存方式,輸出方式以及去前導0的方式是一樣的,但不同之處是減法運算需要借位運算,比如:

在上圖中,我們如果是進行的是順運算還可以,如果進行借位運算,例:

 123

-98  我們需要將十位的2減1並且各位的3加10來完成運算。

再有:如果是出現被減數小於減數的情況,就要將被減數和減數互換,完成運算之後加上負號,比如:3-5,3<5,就要互換3與5的位置在相減,得2,在加上負號,得-2;

總結:

1、定義儲存陣列。

2、被減數和減數確認。並且減法可能出現負數。

3、讀入資料到陣列中。

4、從個位開始模擬豎式減法的過程,完成整個減法。

5、刪除前導 0 。

6、輸出減法的結果。

程式碼以及大多數註釋如下:

#include<bits/stdc++.h>
using namespace std;
char a[10090],b[10090];//用來輸入大數 
int c[10090],d[10090],sum[10090];//用來儲存資料 
char t[10090];//temp變數陣列,用來互換兩個陣列的位置 
int main()
{
    scanf("%s %s",a,b);
    int len1=strlen(a);
    int len2=strlen(b);
    if(len1<len2||len1==len2&&strcmp(a,b)<0)//兩個字串長度不相等或者是相等但是數字有大有小 
    {
        cout<<'-'; 
        strcpy(t,a);
        strcpy(a,b);
        strcpy(b,t);
        len1=strlen(a);//更新長度 
        len2=strlen(b);
    }
    for(int i=0;i<len1;i++)
    {
        c[i]=a[len1-1-i]-'0';//逆序儲存,可以這麼理解,比如存1234,由於需要十進位制進位處理,所以c[0]到c[3]分別是4321 
    }
    for(int i=0;i<len2;i++)
    {
        d[i]=b[len2-1-i]-'0';//同理 
    } 
    for(int i=0;i<len1;i++)
    {
        if(c[i]<d[i])
        {
            c[i+1]--;//借位,高位減一 
            c[i]=c[i]+10;//低位加10 
        }
        sum[i]=c[i]-d[i]; 
        
    } 
    while(sum[len1-1]==0&&len1>1)//用來刪除前導0,比如我們得出的結果是0123,不需要0,刪去就好了,同下面註釋的程式碼 
    len1--;
    /*for (int i=len-1; i>=0; i--) {
        if (0==sum[i] && len>1) {
            len--;
        } else {
            break;
        }
    }*/
    for(int i=len1-1;i>=0;i--)
    printf("%d",sum[i]);
    return 0;
}

三、高精度乘法

題目地址:https://www.luogu.com.cn/problem/P1303

高精度乘法的難度上一層,其實高精度從加到除的難度一直都是遞增的,不說廢話了。

其實對於高精度乘法而言,是有跡可循的規律,我們同樣可以模擬一下:

通過上圖我們可以得到,當a0—b0—c0,

a1—b0—c1      a2-b0-c2     a3-b0-c3,   

a0-b1-c1      a1-b1-c2        a2-b1-c3    a3-b1-c4;

我們分析一下:c[0]=a[0]*b[0],   c[1]=a[1]*b[0]+a[0]*b[1]      c[2]=a[2]*b[0]+a[1]*b[1]     c[3]=a[3]*b[0]+a[2]*b[1]    c[4]=a[3]*b[1];

不難得出 c[i+j]+=a[i]*b[j],為什麼有一個累加的加號呢?其實當初我剛開始接觸的時候也是抱有困惑的,也不難理解,就是,拿上面的圖來講,c[1]=a[1]*b[0]+a[0]*b[1] ,列一個舒適不就是a[1]*b[0]+a[0]*b[1]嘛,我畫一下:

 

這不就是累加嗎,然後在進行錯位相加,進位,取餘處理,就可以啦。

還有一點,乘積的最大長度是不會超過兩個字串長度之和的,所以定義長度直接讓兩個字串的長度相加就可以了。

總結:

1、定義儲存陣列。

2、讀入資料處理。

3、從個位開始模擬豎式乘法的過程,完成整個乘法。

4、刪除前導 0 。

5、輸出結果。

程式碼及注意事項如下:

#include<bits/stdc++.h>
using namespace std;
char a[2010],b[2010];//用來輸入大數 
int c[2010],d[2010];//用來儲存資料 
int sum[4020];//為什麼用4020的長度呢,因為相乘之後資料的長度翻倍,這是我親愛的老師告訴我的,嘻嘻 
int main()
{
    scanf("%s %s",a,b);
    int len1=strlen(a);
    int len2=strlen(b);
    int len3=len1+len2;//乘積的總長度是不會超過兩個長度之和的 
    for(int i=0;i<len1;i++)
    {
        c[i]=a[len1-1-i]-'0';//逆序儲存,可以這麼理解,比如存1234,由於需要十進位制進位處理,所以c[0]到c[3]分別是4321 
    }
    for(int i=0;i<len2;i++)
    {
        d[i]=b[len2-1-i]-'0';//同理 
    } 
    for(int i=0;i<len1;i++)
    {
        for(int j=0;j<len2;j++)
        {
            sum[i+j]+=c[i]*d[j];//細細體會
            sum[i+j+1]+=sum[i+j]/10;//進位
            sum[i+j]%=10;//每位超不過10 
        }
    } 
    while(sum[len3-1]==0&&len3>1)//用來刪除前導0,比如我們得出的結果是0123,不需要0,刪去就好了,同下面註釋的程式碼 
    len3--;
    /*for (int i=len-1; i>=0; i--) {
        if (0==sum[i] && len>1) {
            len--;
        } else {
            break;
        }
    }*/
    for(int i=len3-1;i>=0;i--)
    printf("%d",sum[i]);
    return 0;
}

另外,高精度乘法要特別感謝我的老師,要不是我的老師對我進行了及時的指導和糾正,我高精度乘法就有可能卡一下午(加晚上)

四、高精度除法

高精度除法是分兩個部分的其實,分別是高精度除以低精度(難度稍微較小),高精度除以高精度(難度較大且程式碼較為繁瑣,新手不建議入手)

小聲bb:雖然我是學的c,但是python自帶高精度真好用,2,3行程式碼搞定┭┮﹏┭┮;

(1).高精度除低精度

題目連結:https://www.luogu.com.cn/problem/P1480

高精度除以低精度不太好總結說實話,但是要進行一下覆盤還是總結一下吧,畢竟在腦子中過一遍還是比不過要好的;

為什麼要拿這個題來做高單除的例題,高高除不行嗎?高高除可以,但是這個題的資料範圍要注意:

被除數比除數大的多並且被除數很大而除數相對來說較小,在long long的範圍之中,所以可以用高單除;

我們首先分析,在我們日常中是怎麼計算除法的,我們來模擬一下:

我們採取的是逐位試商的方法,拿上圖來說,4567/23,首先是4/23,商0餘4,45/23商2餘22,226/23.....一直到197/23商9餘13。這就是逐位試商的模擬過程。

也如這樣

但是很顯然,計算機並不能進行逐位試商的方法,所以,我們需要對計算機進行減法模擬

並且我們要注意,除法不同於加法和減法還有乘法,我們把儲存方式改為正序儲存即可,並且要注意在定義長度的時候要用long long,要不然可能會造成資料錯誤導致WA(親身實踐);

試商過程:
1、將除數移動和被除數對齊,位數不夠時,補 0。

2、利用被除數減去除數,一直減到被除數小於除數,減的次數,就是“試商”的結果,每移動一次。

3、重複上述步驟,一直到被除數和除數的位數相等為止。

舉例說明:
舉一個例子,比如 524134 除以 123,結果是 4261 ,餘數為 31。

1、第一位 4 的來源是我們把 524 和 123 對齊,然後進行迴圈減法,迴圈了 4 次,餘 32;

2、將 32134 的前三位 321 繼續和 123 對齊,迴圈減法 2 次,餘 75;

3、把 7534 的前三位 753 和 123 對齊,迴圈減法 6 次,餘 15;

4、將 154 和 123 對齊,只能減 1 次,餘 31。

所以 524134 除以 123,結果是 4261,餘數為 31。

上面思路借鑑自一位大佬,真的犇,不得不服。

總結

1、定義儲存陣列。

2、讀入資料處理,正序儲存

3、試商過程。

4、刪除前導 0 ,正序刪除,從sum【0】開始一旦發現是0就停止並且長度要小於len1;

5、輸出結果。

程式碼及注意事項如下:

#include<bits/stdc++.h>
using namespace std;
char a[5010];//用來儲存資料
int c[5010];//轉化 
long long b,x; //注意用long long  
int sum[5010];
int main()
{
    scanf("%s %lld",a,&b);
    long long len1=strlen(a);
    for(int i=0;i<len1;i++)
    {
        c[i]=a[i]-'0';//正序儲存即可,從高位往低位逐步試商就可以了 
    }
    for(int i=0;i<len1;i++)
    {
        sum[i]=(x*10+c[i])/b;//每次和除數相除得到商 ,不斷試商儲存在sum[i]裡, 
        x=(x*10+c[i])%b;//餘數 
    }
    long long len2=0 ;//用longlong定義並且正序開始遍歷 
    while(sum[len2]==0&&len2<len1)//用來刪除前導0,比如我們得出的結果是0123,不需要0,刪去就好了,同下面註釋的程式碼 
    len2++;
    /*for (int i=len-1; i>=0; i--) {//即使根據題意做出調整,不寫了 
        if (0==sum[i] && len>1) {
            len--;
        } else {
            break;
        }
    }*/
    for(int i=len2;i<len1;i++)//正序輸出 
    printf("%d",sum[i]);
    return 0;
}

(2).高精度除以高精度

這麼嘛,掌握的不是很熟,完全掌握之後在總結吧。

相關文章