C++算法:高精度計算


前言

內容純屬原創,如有雷同,純屬巧合!

如轉載請注明出處!

參考題目

洛谷

https://www.luogu.com.cn/problem/P1601

OpenJudge

http://noi.openjudge.cn/ch0106/10/

一、高精度的普及內容

在C++中,數據類型最高也就只可定義19位數字(好像吧),但Python不同,Python自帶高精度,這也是比賽不支持Python的一部分原因。

但是自從用了高精度之后,麻麻再也不用擔心上限不夠了!

高精度可以隨意定義位數

二、高精度所涉及的基礎內容

1.高精度所涉及的基礎內容有:數組、字符串、以及基礎計算等

2.涉及很多基礎,如for循環、while循環、if、以及一些邏輯(讓人頭疼的瞎搞),不過相信大家這些都沒有問題(除了我)。

三、基本邏輯和思想

我們先以加法做例子(加法:我不要做栗子!我要……“唉嗎真香!”來自作者稚嫩可愛的吃飯聲)

咳咳,

我們先來手動算一個加法算式(別告訴我你不會算)

高精度計算

好的我們來分析一下(回顧一下小學豎式加法的做法)

右起第1位,3+8=11,寫1進1

右起第2位,3+8=11,寫2進1(本位的1加上進位的1得出2)

右起第3位,3+1=4,寫5(本位的4加上進位的1得出5)

右起第4位,2+4=6

右起第5位,5=5

但是要注意,在C++里可不能像第5位這樣,因為這樣會越界,所以我們第5位的真正算式是:0+5=5

 

正常時候,我們計算加法豎式有什么要點呢?

來來來我教一下一年級知識(啊不,二年級)

(1)從右邊開始計算

(2)可能會有進位

(3)數位對齊計算

首先我們來看一下,從左邊開始這一條非常簡單,我們只需要存數組的時候倒着存進來,然后輸出倒着輸出就行了

第二條,這也非常簡單,我們只需要將多出來的一位加到后面那一位就行了

第三條更加容易實現,for循環里的i(給你眼神自己體會qwq)

豎式計算到這里

我們來列一下大綱

1.讀入

2.計算

3.輸出

這部分講到這里,具體實現往下看

四、具體邏輯的實現

實現第1步,讀入

但是問題來了,我們這樣去讀,該讀到哪里呢?怎么讀呢?

可能有聰明的老司機小伙伴想到了

可以用

scanf("%1d",a[i]);

來讀進數組中。

可是,這位童鞋,難道你沒有想到,你讀入多長呢?長度咱也不知道哇

所以,我們就要用偉大的萬能的字符串來讀入了

首先我們把字符串名字叫做a1和b1吧(因為我比較習慣把a和b當做數組的名字)

然后cin(其實scanf也可以,這里便於寫(其實是偷懶)才這樣的你肯定沒發現我括號套娃了

那么字符串的長度我們就可以知道了,

int lena=a1.length();
int lenb=b1.length();

這里我把lena當成a數組的長度,lenb當成b數組的長度

用C++自帶的string.length來獲取長度

當然,你如果不喜歡用string,用char也是可以的,那么你這里就應該寫strlen(r);了

然后我們把字符串轉化成數組元素

for(int i=0;i<lena;i++)
    a[i]=a1[lena-i]-48;//玄學轉換術
for(int i=0;i<lenb;i++)
    b[i]=b1[lenb-i]-48;//玄學*2

這里給大家講解一下

-48這個我就不說了,因為48在ASCII碼里代表的是'0',減出來正好可以得出數

上面我們說過了,兩個數組是倒着存的,所以我們這里的lena-i和lenb-i都是輔助我們倒着存的

給大家簡單寫寫

高精度計算

那么我們可以清晰的看出,對比原數組和讀進的數組來看的話,完全就是相反的。

別忘了我們的for+數組也是從右邊開始計算的,這樣讀入部分就算完結了

 

來實現第2步

我們回顧一下第一張豎式計算的圖片,

最后得出來的結果絕對不可能大於兩個數最大的位數+1

比如999+9999

兩個數中位數最多的是4位

4+1=5

那么這兩個數相加絕對不可能大於5。

 

那么和的長度有了,就來計算了

我們暫時把和的數組叫做c(其實和存到b也可以,但是我懶的去搞了,就先這樣吧)

這里給大家提一個小問題

我們是應該算完一個數進一次位還是全部算完再進位呢?

A、算完一個數進一次位

B、全部算完再進位

 

答案是:AB

為什么呢?

比如選A

    2 3 4

+   9 9 9 

------------

  0 0 1 3

  0 1 2 3

  1 1 3 3

------------

  1 2 3 3

但如果你是存到b數組中的

第一步:計算

     2   3 4

+   9 10 3 

第二步:進位

       2 3 4

+   10 3 3 

第三步:計算

       2 3 4

+   12 3 3 

第四步:進位

       2 3 4

+  1 2 3 3 

好的我們暫時計算到這里

開始真正的計算(代碼寫)

首先吧lenc也就是c數組最長長度算出來

int lenc=max(lena,lenb)+1;

然后進行c數組的計算

我們這里用先算完在進位的方法,思路會比較清晰

for(int i=1;i<=lenc;i++){
    c[i]=a[i]+b[i];
}

然后是進位部分

這一塊我都不講了,太簡單了,主要講思路

for(int i=1;i<=lenc;i++){
    c[i+1]+=c[i]/10;
    c[i]=c[i]%10;
}

還沒結束!

這里提供另一種lenc的計算方法!

在開始定義的地方

也就是

int lenc=max(lena,lenb)+1;

我們可以這樣寫

int lenc=max(lena,lenb);

其實就是少了個+1,但是思路卻大大改變

原先的寫法,是進位就剛好的想法,如果不進位的話數組最前面也就是c[lenc]的位置就會多一個0

而且如果題目上沒說讀入會有前導0的話(前導0就比如0000003333333333333333333333這種數,輸出的時候要特地把前導0過濾一遍)

這種方法需要特意的去除前導0(用while或者if)

while去前導0的寫法我后面會講,if就直接這樣

if(!c[lenc])len--;

但如果用第二種的話,那么思路會大大改變

模擬這道題不進位

然后計算完進位完之后,加一個

 

if(c[lenc+1])len++;

 

思路:發現這其實進位了,那么和的長度應該是進位后的長度,也就是原先的lenc+1

 

不多廢話,我剛剛好像承諾了你們要講while去前導0的

那好吧,我講講

來個例子

0  0  0  1  2  0

這個數要去前導0

那么我們從左邊開始看,左起第一個數是0

去一個前導0位數就減少了1,那么我們直接減位數就行了,也就是lenc--;

0  0  1  2  0

繼續繼續

當前左起第一位發現還是0

去0

0  1  2  0

左起第一位還是0

去0

1  2  0

左起第一位不是0了

那么跳出循環

為啥要跳出循環呢?

不然容易去掉結尾的0,比如原本的120變成了12就錯了

while(!c[lenc]&&lenc>1)lenc--;

前面的!c[lenc]表示這位是0

lenc>1要保證剩個0,不然就啥都不輸出了,比如00000,最后lenc長度就剩下0了。。。。

 

最后第3步,輸出要進行一個變動

我們讀入的時候是反着讀入的,這意味着我們要反着輸出

那么想到這里就很輕松了

for(int i=lenc;i>=1;i--)cout<<c[i];

這里用的是倒着循環

同樣,你也可以和讀入一樣寫

for(int i=1;i<=lenc;i++)cout<<c[lenc-i];

方法任你選擇,就看個人愛好了

 

練習題在前面有寫了,也可以自己去洛谷、Openjudge里搜,題非常多。

 

完整代碼:(頭文件我用的萬能頭,大家要不要養成萬能頭的習慣哦!)

#include<bits/stdc++.h>
using namespace std;
string a1,b1;
int a[1000],b[1000],c[1000];
int main(){
  cin>>a1>>b1;
  int lena=a1.length();
  int lenb=b1.length();
  for(int i=1;i<=lena;i++)
  	a[i]=a1[lena-i]-48;
  for(int i=1;i<=lenb;i++)
  	b[i]=b1[lenb-i]-48;
    int lenc=max(lena,lenb);
    for(int i=1;i<=lenc;i++){
    	c[i]=a[i]+b[i];
    }
    for(int i=1;i<=lenc;i++){
    	c[i+1]+=c[i]/10;
  	c[i]=c[i]%10;
    }
    if(c[lenc+1])lenc++;
    while(c[lenc]==0&&lenc>1)lenc--;
    for(int i=lenc;i>=1;i--)cout<<c[i];
    return 0;
}

 

五、高精度拓展

高精度不僅能實現加法,還可以實現減法、乘法、除法以及求模

這里我簡單講一下運算時的思路

 

1.減法

lenc我們就直接求被減數和減數的最大位數就行了

我們在計算時,for里先寫一個if,也就是如果被減數比減數大,那么被減數的前一位-1,當前位+10,然后if外面寫減就行了,這個都不用處理進位

但是一般的減法要考慮負數,我們只要在前面讀入完a1和b1的時候這樣寫

if((a1<b1&&a1.length()==b1.length())||a1.length()<b1.length()){

  cout<<"-";

  string temp;

  temp=a;

  a=b;

  b=temp;

}

 

這里判斷了兩種情況,一種是兩數位數相等但是大小不一樣,另一種是位數不一樣

然后如果成立的話就把a和b互換,輸出負號

因為被減數小於減數,那么差就相當於負的減數減被減數

比如2-3,他們的差就等於-(3-2)

 

2.乘法

乘法就很簡單啊

乘法分兩種

高精度*單精度

高精度*高精度

高精度*單精度就相當於加法

比如100*8就很簡單

這里示范高精度*高精度

和加法一樣,不過是需要二重循環來寫

for(int i=1;i<=lena;i++){
    for(int j=1;j<=lenb;j++){
        c[i+j-1]+=a[i]*b[j];
    }
}

不過計算要改

因為要很多數相加

比如11*11

那么就是11+110

要很多數相加所以是+=

然后lenc的話就很簡單了

int lenc=lena+lenb+1;

這個lenc大家自己品吧,和加減的差不多個道理

 

3.除法

除法也分兩種,

高精度/單精度

高精度/高精度

高精度/單精度也很簡單

就是挨個位除而已,除不盡落下來

高精度/高精度就比較麻煩了

先比較位數,在挨個位比較大小

如果能除就除,就相當於比較完大小之后減,但這個減也是高精度-高精度的,減完再看看能不能減,這樣循環,如果不能減了就落下來,下一次循環

但最后他可能會讓你保留小數之類的

那么你就要小心了,要把小數的地方留出來

所以這個地方要謹慎

 

5.取模

這個大概和除法沒啥區別吧。。。。不就是把余數留出來嗎

反而比除法簡單

都不用留出小數的空間了

 

六、總結

碼了好半天,真累啊qwq

求給個頂或者占樓ddd

咳咳,那么正式總結一下

我們這里用了字符串轉數字、挨個位計算之類的

總之會了一個,其他幾個想想也就能算出來

易錯點:

(1)保留小數

(2)考慮負數

(3)進位借位

(4)數據范圍(數組大小)

(5)lenc的長度

(6)位數

(7)去不去(或者有沒有)前導0

(8)倒着輸入、輸出

(9)i的起始位置要確定

 

那么今天到這里,歡迎下次再來看qwq


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM