高精度算法

高精度

1.什麼是高精度

高精度算法,屬於處理大數字的數學計算方法。在通常的科學計算中,會常常算到小數點後幾百位或者更多,固然也多是幾千億幾百億的大數字。通常這類數字咱們統稱爲高精度數,高精度算法是用計算機對於超大數據的一種模擬加,減,乘,除,乘方,階乘,開方等運算。對於很是龐大的數字沒法在計算機中正常存儲,因而,咱們能夠將這個數字拆開,拆成一位一位的,或者是幾位幾位的存儲到一個數組中, 用一個數組去表示一個數字,這樣這個數字就被稱爲是高精度數。高精度算法就是能處理高精度數各類運算的算法,但又因其特殊性,故從普通數的算法中分離,自成一家。c++

對於這類問題,不要期望long double這些東西了,基本數據類型不可能存的下。咱們能夠把這兩個數當成字符串輸入到數組中,而後模擬手動的豎式運算(不會的話,回去上小學)得出結果。web

說白了,高精度計算就是解決long long也解決不了的問題。算法

2.高精度的做用

正如上面所說的,高精度的做用就是對於一些異常之大的數字進行加減乘除乘方階乘開方等運算。好比給你一道a+b的題目,讀入a和b,讓你輸出它們的和,但a和b的範圍都是小於等於10的6666次方,這個時候你就只能用高精度了。數組

3.高精度讀入處理數據

當一個數據很大的時候,咱們用一個整數類型是存不下的,因此咱們能夠先用一個字符串輸入,這樣就能夠輸入很長的數,而後再利用字符串函數和操做運算,將每一位數取出,存入一個數組裏,咱們用數組裏的每一位表示這個數的每個數位。svg

例如:998244353用數組儲存下來,a{3,5,3,4,4,2,8,9,9},通常是倒着存(從低位到高位,由於整數沒有除個位如下的數位,但你的最高位還能夠進位,那麼你就又要開一個位置來存這個新的最高位)。函數

高精度讀入Code大數據

char s[6666];
int a[6666];
 
int main(){
    scanf("%s",s+1);//用字符串讀入
    len=strlen(s+1);//這個數的長度爲len
    for(int i=1;i<=len;i++){
        a[i]=s[len-i+1]-'0';//倒敘儲存,每一位存一個數
    }
    return 0;
}

高精度輸出Codespa

int a[6666]
 
void write(int a[]){
    for(int i=lena;i>0;i--){
        printf("%d",a[i]);//一位一位輸出這個數
    }
}

4.高精度比較大小

處理完數據以後,假設咱們要比較兩個數那個大怎麼辦呢?code

咱們先模擬比較1314520和1314530,首先咱們看兩個數的長度(即len1和len2)若是哪一個數的len更長一點,那麼這個數確定要比另外一個數大,不然咱們才繼續比較下去,這裏兩個數的長度是同樣的,因此接下來咱們就看這兩個數的最高位(即1和1),相等,則繼續比較下一位(3和3),也同樣,繼續比較下一位…直到比到十位的時候(2和3),由於2<3,因此第一個數<第二個數,直接退出。xml

因此,高精度比較大小的步驟大體以下:

一、比較兩個數的長度,長度更長的數越大。

二、若是兩個數長度相等,那麼就從高位到低位一位一位比較,若是某一位數字不一樣時,較大的數大。不然繼續比較下一位。

三、若是比到最後都沒有比出誰大誰小,就說明這兩個數同樣大。

高精度比較大小Code

//比較a和b的大小,若是a>b則返回真,不然返回假
int a[6666],b[6666];
 
int compare(){
    if(lena>lenb) return 1;//lena表示a這個數的長度,lenb則表示b的長度
    if(lenb>lena) return 0;//步驟1
    for(int i=lena;i>0;i--){//從高位到底位一位一位比較
        if(a[i]>b[i]) return 1;
        if(b[i]>a[i]) return 0;
    }//步驟2
    return 0;//步驟3,a=b,即a不大於b
}

5.高精度處理進位與借位

1、那麼咱們怎麼處理進位呢?

其實也很簡單,咱們再來模擬一下(模擬大法好,不會也得會),1439+887的時候,首先咱們最低位相加,獲得16,那麼答案最低位就是6,再進個1,而後兩數的十位相加,3+8=11,而後再加上進位的1,就是11+1=12,因此答案十位就是2,再進1,4+8+1=13,答案百位是3,進1,1+0+1=2,答案千位是2。因此結果就是6232!哦,不對反了,是2326,呵呵,這裏要注意一下,輸出的時候是倒着輸出的,千萬不要忘了。

總結一下,進位的步驟大體以下:

一、將當前位置的數相加,當前位的結果就是當前結果除以10的餘數。

二、再講當前結果除以10加到高位,表示進位。

注意:有同窗可能會有疑問,爲何必定要倒着儲存這個數呢?順着存不是更好嗎?這裏我舉一個很是簡單的例子,好比10+90,誰都知道答案是100,那麼咱們來看看順着儲存和倒着儲存有什麼區別:

1.順着存:a={1,0},b={9,0},當a的最高位(即數組第1位)加上b的最高位(即數組第2位)時咱們是否是得進位?!?可是a的最高位是數組的第一位,那麼咱們要進位到a數組的第幾個位置呢?第0個位置?太複雜了。仍是存在第一個位置並把全部的數往數組右邊移動一位?那更不行,時間複雜度又過高,因此很差辦。

2.倒着存:a={0,1},b={0,9},當a的最高位(即數組第2位)加上b的最高位(即數組第2位)時咱們一樣要進位,這個時候就好辦了,直接進位到數組的第三位就行了,因此答案的數組就是{0,0,1},倒過來輸出答案100。

高精度進位Code

int a[6666],b[6666],c[6666];
int lena,lenb,lenc;
 
int main(){
    lenc=max(lena,lenb);
    for(int i=1;i<=lenc;i++){
        c[i]=c[i]+a[i]+b[i];
        c[i+1]=c[i]/10;
        c[i]=c[i]%10;
    }
    while(c[lenc+1]>0) lenc++;//答案的長度有時還會增長
}

2、接下來說講當減法的時候如何借位

一、將當前位置的數向減。

二、若是結果大於或等於0就直接做爲當前位的答案。

三、不然將結果加10做爲當前位的答案,在將高位的數-1便可。

高精度借位Code

int a[6666],b[6666],c[6666];
int lena,lenb,lenc;
 
int main(){
    lenc=max(lena,lenb);
    for(int i=1;i<=lenc;i++){
        c[i]=c[i]+a[i]-b[i];
        if(c[i]<0) c[i]=c[i]+10,c[i+1]=-1;
    }
    while(c[lenc]==0&&lenc>1) lenc--;//細節,若是a-b結果爲0,那麼也要輸出一個0
}

6.高精度加法
至此,就能夠進行任意你想進行的運算了,首先咱們來看看加法,其實上面的代碼已經差很少寫出來了。

高精度加法Code

#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
int a[6666],b[6666],c[6666];
int lena,lenb,lenc;
char s1[6666],s2[6666];
 
int main(){
    scanf("%s %s",s1+1,s2+1);
    lena=strlen(s1+1);
    lenb=strlen(s2+1);
    for(int i=1;i<=lena;i++) a[i]=s1[lena-i+1]-'0';
    for(int i=1;i<=lenb;i++) b[i]=s2[lenb-i+1]-'0';
    lenc=max(lena,lenb);
    for(int i=1;i<=lenc;i++){
        c[i]=c[i]+a[i]+b[i];
        c[i+1]=c[i]/10;
        c[i]=c[i]%10;
    }
    while(c[lenc+1]>0) lenc++;
    for(int i=lenc;i>0;i--) printf("%d",c[i]);
}

7.高精度減法
高精度減法Code

#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
int a[6666],b[6666],c[6666];
int lena,lenb,lenc;
char s1[6666],s2[6666];
 
int main(){
    scanf("%s %s",s1+1,s2+1);
    lena=strlen(s1+1);
    lenb=strlen(s2+1);
    if(lenb>lena||(lena==lenb&&s2>s1)){//若是第二個數比第一個數大,那麼結果是負數
    	printf("-");
    	swap(s1,s2);//swap是C++自帶函數能夠直接調用
    	swap(lena,lenb);//別忘了交換長度
    }
    for(int i=1;i<=lena;i++) a[i]=s1[lena-i+1]-'0';
    for(int i=1;i<=lenb;i++) b[i]=s2[lenb-i+1]-'0';
    lenc=max(lena,lenb);
    for(int i=1;i<=lenc;i++){
        c[i]=c[i]+a[i]-b[i];
        if(c[i]<0) c[i]=c[i]+10,c[i+1]=-1;
    }
    while(c[lenc]==0&&lenc>1) lenc--;
    for(int i=lenc;i>0;i--) printf("%d",c[i]);
}

8.高精度乘法
1.高精度乘以單精度
高精度乘以單精度Code

#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
int a[6666],b;
int lena;
char s1[6666];
 
int main(){
    scanf("%s %d",s1+1,&b);
    lena=strlen(s1+1);
    for(int i=1;i<=lena;i++) a[i]=s1[lena-i+1]-'0';
    for(int i=1;i<=lena;i++) a[i]*=b;
    for(int i=1;i<=lena;i++){
    	a[i+1]+=a[i]/10;
    	a[i]%=10;
    }
    while(a[lena+1]>0) lena++;
    for(int i=lena;i>0;i--) printf("%d",a[i]);
}

2.高精度乘以高精度
高精度乘以高精度Code

#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
int a[6666],b[6666],c[6666];
int lena,lenb,lenc;
char s1[6666],s2[6666];
 
int main(){
    scanf("%s %s",s1+1,s2+1);
    lena=strlen(s1+1);
    lenb=strlen(s2+1);
    for(int i=1;i<=lena;i++) a[i]=s1[lena-i+1]-'0';
    for(int i=1;i<=lenb;i++) b[i]=s2[lenb-i+1]-'0';
    lenc=lena+lenb-1;
    for(int i=1;i<=lena;i++){
    	for(int j=1;j<=lenb;j++){
    		c[i+j-1]+=a[i]*b[j];
    		c[i+j]+=c[i+j-1]/10;
    		c[i+j-1]%=10;
		}
	}
    while(c[lenc+1]>0) lenc++;
    for(int i=lenc;i>0;i--) printf("%d",c[i]);
}

9.高精度除法

1.高精度除以單精度
手動模擬一下,咱們只要記錄一個r,表示當前的餘數,而後不斷除就能夠了。

高精度除以單精度Code

#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
int a[6666],b,r;
int lena;
char s1[6666];
 
int main(){
    scanf("%s %d",s1+1,&b);
    lena=strlen(s1+1);
    for(int i=1;i<=lena;i++) a[i]=s1[lena-i+1]-'0';
    for(int i=lena;i>0;i--){
    	r=r*10+a[i];
    	a[i]=r/b;
    	r=r%b;
    }
    while(a[lena]==0&&lena>1) lena--;
    for(int i=lena;i>0;i--) printf("%d",a[i]);
}

2.高精度除以高精度
不少人都不知道高精度如何整除以一個高精度,那麼這裏我就講一下個人方法吧,可能不是最優的。

咱們知道除法是乘法的逆運算,那麼咱們爲何不能夠看作是咱們如今要求一個數乘以除數等於被除數呢?那麼枚舉確定是不行的,這個時候咱們就要用到二分啦(二分大發好啊~),沒錯高精度二分商,實際上就是一個高精度加法(mid=l+r),而後高精度除以單精度(mid/2),最後再高精度減法(r=mid-1)就能夠實現二分了,咱們二分出來的數直接高精度乘以高精度判斷一下就能夠了(代碼中數組中的第0個位置表示此數的長度,瞬間暴露PC黨…)。

高精度除以高精度Code

#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
int a[6666],b[6666],ans[6666],t[6666],l[6666],r[6666],mid[6666];
char s1[6666],s2[6666];
 
int compare(int a[],int b[]){
    if(a[0]>b[0]) return 0;
    if(a[0]<b[0]) return 1;
    for(int i=a[0];i>0;i--){
        if(a[i]>b[i]) return 0;
        if(a[i]<b[i]) return 1;
    }
    return 1;
}
 
void add(){
    mid[0]=max(l[0],r[0]);
    for(int i=1;i<=mid[0];i++) mid[i]=l[i]+r[i];
    while(mid[mid[0]+1]>0) mid[0]++;
    for(int i=1;i<=mid[0];i++){
        mid[i+1]+=mid[i]/10;
        mid[i]%=10;
    }
    while(mid[mid[0]+1]>0) mid[0]++;
}
 
void times(){
    memset(t,0,sizeof(t));
    t[0]=b[0]+mid[0]-1;
    for(int i=1;i<=b[0];i++){
        for(int j=1;j<=mid[0];j++){
            t[i+j-1]+=b[i]*mid[j];
            t[i+j]+=t[i+j-1]/10;
            t[i+j-1]%=10;
        }
    }
    while(t[t[0]+1]>0) t[0]++;
}
 
void div(){
    int r=0;
    for(int i=mid[0];i>0;i--){
        r=r*10+mid[i];
        mid[i]=r/2;
        r%=2;
    }
    while(mid[mid[0]]==0) mid[0]--;
}
 
void left(){
    for(int i=0;i<=mid[0];i++) l[i]=ans[i]=mid[i];
    l[1]++;
    for(int i=1;i<=l[0];i++){
        l[i+1]+=l[i]/10;
        l[i]%=10;
    }
}
 
void right(){
    for(int i=0;i<=mid[0];i++) r[i]=mid[i];
    r[1]--;
    for(int i=1;i<=r[0];i++){
        if(r[i]<0){
            r[i+1]--;
            r[i]+=10;
        }
    }
}
 
int main(){
    scanf("%s %s",s1+1,s2+1);
    a[0]=r[0]=strlen(s1+1);
    b[0]=strlen(s2+1);
    for(int i=1;i<=a[0];i++) a[i]=r[i]=s1[a[0]-i+1]-'0';
    for(int i=1;i<=b[0];i++) b[i]=s2[b[0]-i+1]-'0';
    l[0]=ans[0]=1;
    while(compare(l,r)){
        add();
        div();
        times();
        if(compare(t,a)) left();
        else right();
    }
    for(int i=ans[0];i>0;i--) printf("%d",ans[i]);
    return 0;
}

碼打的有點醜,不要介意…

10.高精度開平方
高精度開平方其實也還算比較簡單,有一次考試就有一道原題,我一怒之下把它給切了。

沒錯,仍是二分。二分答案。利用高精度加法和高精度除以單精度能夠實現二分的效果,而後直接高精度乘法乘起來再高精度比較一下大小,再用高精度減法移動一下l和r就能夠了。其實這也算是高精度比較綜合的作法了。碼量雖然驚人,但其實高精度除以高精度改一改就行了

高精度開平方Code

#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
int a[6666],b[6666],ans[6666],t[6666],l[6666],r[6666],mid[6666];
char s1[6666],s2[6666];
 
int compare(int a[],int b[]){
    if(a[0]>b[0]) return 0;
    if(a[0]<b[0]) return 1;
    for(int i=a[0];i>0;i--){
        if(a[i]>b[i]) return 0;
        if(a[i]<b[i]) return 1;
    }
    return 1;
}
 
void add(){
    mid[0]=max(l[0],r[0]);
    for(int i=1;i<=mid[0];i++) mid[i]=l[i]+r[i];
    while(mid[mid[0]+1]>0) mid[0]++;
    for(int i=1;i<=mid[0];i++){
        mid[i+1]+=mid[i]/10;
        mid[i]%=10;
    }
    while(mid[mid[0]+1]>0) mid[0]++;
}
 
void times(){
    memset(t,0,sizeof(t));
    t[0]=mid[0]+mid[0]-1;
    for(int i=1;i<=mid[0];i++){
        for(int j=1;j<=mid[0];j++){
            t[i+j-1]+=mid[i]*mid[j];
            t[i+j]+=t[i+j-1]/10;
            t[i+j-1]%=10;
        }
    }
    while(t[t[0]+1]>0) t[0]++;
}
 
void div(){
    int r=0;
    for(int i=mid[0];i>0;i--){
        r=r*10+mid[i];
        mid[i]=r/2;
        r%=2;
    }
    while(mid[mid[0]]==0) mid[0]--;
}
 
void left(){
    for(int i=0;i<=mid[0];i++) l[i]=ans[i]=mid[i];
    l[1]++;
    for(int i=1;i<=l[0];i++){
        l[i+1]+=l[i]/10;
        l[i]%=10;
    }
}
 
void right(){
    for(int i=0;i<=mid[0];i++) r[i]=mid[i];
    r[1]--;
    for(int i=1;i<=r[0];i++){
        if(r[i]<0){
            r[i+1]--;
            r[i]+=10;
        }
    }
}
 
int main(){
    scanf("%s",s1+1);
    a[0]=r[0]=strlen(s1+1);
    for(int i=1;i<=a[0];i++) a[i]=r[i]=s1[a[0]-i+1]-'0';
    l[0]=ans[0]=1;
    while(compare(l,r)){
        add();
        div();
        times();
        if(compare(t,a)) left();
        else right();
    }
    for(int i=ans[0];i>0;i--) printf("%d",ans[i]);
    return 0;
}

11.高精度開n次方
稍加處理便可:開平方時將二分的答案乘兩次,那麼開n次方不就是將二分出來的答案乘n次嗎?

高精度開n次方Code

#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
int n,a[6666],b[6666],ans[6666],t[6666],l[6666],r[6666],mid[6666];
char s1[6666];
 
int compare(int a[],int b[]){
    if(a[0]>b[0]) return 0;
    if(a[0]<b[0]) return 1;
    for(int i=a[0];i>0;i--){
        if(a[i]>b[i]) return 0;
        if(a[i]<b[i]) return 1;
    }
    return 1;
}
 
void add(){
    mid[0]=max(l[0],r[0]);
    for(int i=1;i<=mid[0];i++) mid[i]=l[i]+r[i];
    while(mid[mid[0]+1]>0) mid[0]++;
    for(int i=1;i<=mid[0];i++){
        mid[i+1]+=mid[i]/10;
        mid[i]%=10;
    }
    while(mid[mid[0]+1]>0) mid[0]++;
}
 
void times(){
    for(int i=0;i<=mid[0];i++) b[i]=mid[i];
    for(int k=1;k<=n-1;k++){
        memset(t,0,sizeof(t));
        t[0]=b[0]+mid[0]-1;
        for(int i=1;i<=b[0];i++){
            for(int j=1;j<=mid[0];j++){
                t[i+j-1]+=b[i]*mid[j];
                t[i+j]+=t[i+j-1]/10;
                t[i+j-1]%=10;
            }
        }
        while(t[t[0]+1]>0) t[0]++;
        for(int i=0;i<=t[0];i++) b[i]=t[i];
    }
}
 
void div(){
    int r=0;
    for(int i=mid[0];i>0;i--){
        r=r*10+mid[i];
        mid[i]=r/2;
        r%=2;
    }
    while(mid[mid[0]]==0) mid[0]--;
}
 
void left(){
    for(int i=0;i<=mid[0];i++) l[i]=ans[i]=mid[i];
    l[1]++;
    for(int i=1;i<=l[0];i++){
        l[i+1]+=l[i]/10;
        l[i]%=10;
    }
}
 
void right(){
    for(int i=0;i<=mid[0];i++) r[i]=mid[i];
    r[1]--;
    for(int i=1;i<=r[0];i++){
        if(r[i]<0){
            r[i+1]--;			
            r[i]+=10;
        }
    }
}
 
int main(){
    scanf("%d\n",&n);
    scanf("%s",s1+1);
    a[0]=r[0]=strlen(s1+1);
    for(int i=1;i<=a[0];i++) a[i]=r[i]=s1[a[0]-i+1]-'0';
    l[0]=ans[0]=1;
    while(compare(l,r)){
        add();
        div();
        times();
        if(compare(t,a)) left();
        else right();
    }
    for(int i=ans[0];i>0;i--) printf("%d",ans[i]);
    return 0;
}

12.高精度小技巧——壓位
當咱們學完全部高精度運算的時候,咱們依然會發現一個問題,就是它的數會給你很大,以致於當你一位一位進行運算的時候時間會超時!那怎麼辦呢?難道真的沒有解決的辦法了嗎?那你就過小看高精度了,其實解決這個問題的方法就是——壓位。壓位是什麼?聽我慢慢道來。咱們能夠發現致使高精度時間慢的緣由是咱們一位一位將這個數存了下來,那麼咱們可不能夠在數組中的一個位置不止存一個數位呢?答案是確定的。你能夠存兩位,三位,四位…其實咱們壓位的時候最少都要壓十幾位,由於你才壓幾位跟不壓位的時間有什麼區別呢?當咱們壓位的時候,若是你要壓好比16位,那麼必定要記得開long long或int64類型,這樣你才存的下這個十幾位的數,還要注意,當咱們輸出這個數的時候,當一個位置的數不滿你壓的位數時,好比你壓了16位,可是數組當前的位置的這個數只有1位!!!這說明了什麼?說明這個數是由前面的15個0和最後一位構成的,也就是說原來這個數是000000000000000X,可是你只輸出了X,明顯答案是不對的,好比說一個數是5200000000000000001314,那麼你存的是a{ 1314,520000 },當輸出的時候若是你只是按照數組中的數字輸出,那麼你的答案是5200001314,發現了嗎?你少了中間的0!!!因此當咱們輸出的時候,咱們要判斷這個數是不是滿你壓的位數,若是不足,那麼就要補0,可是最高位除外。這就是壓位的基本思路了。

1.高精度壓位讀入處理
高精度壓位讀入處理Code

const int mod=1000000000000000;
char s[6666];
int a[6666];
 
 
int main(){
    scanf("%s",s+1);
    int len=strlen(s+1),st=1;
    for(int i=len;i>0;i--){
        x=x+(s[i]-'0')*st;
        st*=10;
        if(st==mod){
            st=1;x=0;
            a[++a[0]]=x;
        }
    }
}

2.高精度壓位輸出處理
高精度壓位輸出處理Code

const int mod=10000000000000000;
char s[6666];
int a[6666];
 
int main(){
 
    printf("%d",a[a[0]]);
    for(i=a[0];i>0;i--){
        t=a[i],l=0;
        while(t>0){
            t/=10;l++;
        }
        for(j=1;j<=15-l;j++) printf("0");
        printf("%d",a[i]);
    }
}

更多的題目實現須要用已有的知識靈活變通。 以上爲高精度算法大部份內容。但願你能有所收穫。