高精度算法大全


什么是高精度數?
在一般的科學計算中,會經常算到小數點后幾百位或者更多,當然也可能是幾千億幾百億的大數字。一般這類數字我們統稱為高精度數,高精度算法是用計算機對於超大數據的一種模擬加,減,乘,除,乘方,階乘,開放等運算。
對於一個很大的數字N >= 10^ 100, 很顯然這樣的數字無法在計算機中正常存儲。於是, 我們想到了辦法,將這個數字拆開,拆成一位一位的 或者是四位四位的存儲到一個數組中, 用一個數組去表示一個數字。這樣這個數字就被稱謂是高精度數。
對於高精度數,也要像平常數一樣做加減乘除以及乘方的運算,於是就有了高精度算法:
由於計算機輸入計算結果的精度通常受到計算機的限制,如:在雙精度方式下,計算機最多只能輸出16位有效數字,如果超過16位,則只能按浮點形式輸出,另外,一般計算機實數表示的范圍為1038,如果超過這個范圍,計算機就無法表示了。但是我們可以通過一些簡單的辦法來解決這個問題。這就是我們要說的高精度計算機。
一、基本方法:
(一)、數據的接收與存儲
要在計算機上進行高精度計算,首先就應該有精確的輸入,即計算機要精確地接收和存儲數據。
基本思路:
1、以字符串的方式讀入數字並保存在ac字符數中。
2、用strlen函數計算字符數組ac中總字符數(高精度數的位數)並保存在整型數組的a[0]中。
3、將字符串中的字符逐個字符轉化成數字並保存在整型數組a中。這一部分的代碼編寫非常重要,運算都會從低位開始 (先算個位,再算十位,再……)在存儲時需要倒序存儲,也就是個位數存在a[1],十位數存在a[2]最高位存在a[a[0]]。
例如數字4678在數組中存儲結構為:
a[0]    a[1]    a[2]    a[3]    a[4]    a[5]    a[6]    a[7]    a[8]    a[9]
4   8   7   6   4                   
下面的程序dh子函數完成了以字符方式讀入兩個字符串並將其轉化成數字存儲在數組a[]和b[]中。
#include<iostream>
#include<cstring>
using namespace std;
int a[1010],b[1010];         //用於存儲拆解后的數字 
void dh()
{
    int an,bn,i;
    char ac[1010],bc[1010];
    cin>>ac>>bc;
    a[0]=strlen(ac);          //a高精度數的位數存在a[0]中 
    for(i=1;i<=a[0];i++)a[i]=ac[a[0]-i]-'0'; //倒序存儲數字(+、-\*)
    b[0]=strlen(bc);          //b高精度數的位數存在b[0]中 
    for(i=1;i<=b[0];i++)b[i]=bc[b[0]-i]-'0';//倒序存儲數字(+、-\*)
    return;
}
int main()
{
    dh();
    return 0;
}
(二)、運算結果的輸出
a[0]    a[1]    a[2]    a[3]    a[4]    a[5]    a[6]    a[7]    a[8]    a[9]
4   0   0   0   1   0   0   0   0   0
b[0]    b[1]    b[2]    b[3]    b[4]    b[5]    b[6]    b[7]    b[8]    b[9]
3   9   9   9   0   0   0   0   0   0
結果  1   0   0   0   0   0   0   0   0
這里只考慮高精加、高精減、高精乘這類倒序存放的數據的計算結果的輸出,這三類數據由於是倒序存放,而且運算結果的位數很難確定,如1000-999=1,如下表所示
所以此類運算結果的輸出一般都需要一個去除前導0的過程,也就是說忽略掉數組中后面所有的無效0,定位到數組中最右一個非0數字,再從最右側的非0位開始依次輸出數字。
具體代碼:
i=1000(偷懶點的可以直接設數據位數上限)
while(a[i]==0)i--;   //去除前導0
    while(i>0)cout<<a[i--];
(三)、高精度數的加法運算(兩個非負整數)
基本思路:
1、先以前面介紹的方式以讀字符串的方式讀取兩個加數並轉化存儲到整型數組a和b中。
2、用一個變量c存儲運算結果的最大位數(兩個加數中的大數字的位數+1,+1是考慮到最后結果最高位位有可能有進位的問題,如50+990=1040)
3、用一個變量k來存儲有可能的進位情況,初始化為0。
4、從最低位到最高位依次相加,將相加結果的個位保存在當前位,進位情況記錄到k(0無進位,1 進位1),以便在進行下一次運算的時候把進位加進去。
具體代碼如下:
void gjjia()
{
    int i,c,k=0,t;
    c=a[0]>b[0]?a[0]+1:b[0]+1;
    for(i=1;i<=c;i++)
{
     t=a[i]+b[i]+k;
     a[i]=t%10;
     k=t/10;
}
     return;
}
(四)、高精度減法運算(兩個數都是非負整數且被減數一定比減數大)
基本思路:
1、先以前面介紹的方式以讀字符串的方式讀取兩個加數並轉化存儲到整型數組a和b中。
2、用一個變量c存儲運算結果的最大位數(因為默認被減數比減數大,所的最大位數就是被減數的位數,減法運算結果的最高位不可能產生進位,不需要+1)
3、用一個變量k來存儲有可能的借位情況,初始化為0。
4、從最低位到最高位依次相加,將相加結果的個位保存在當前位,進位情況記錄到k(當前位的被減數比減數小需要向高位借位k=-1;否則k=0),以便在進行下一次運算的時候把借位減掉。
具體代碼如下:
void gjjian()
{
    int i,c,k=0,t;
    c=a[0];
    for(i=1;i<=c;i++)
    {
        if(a[i]+k>=b[i]){a[i]=a[i]+k-b[i];k=0;}
          else{a[i]=a[i]+10+k-b[i];k=-1;}
    }
}
(五)、高精度減法運算(兩個數都是非負整數但不確定兩者大小)
基本思路:由於不確定被減數和減數哪個更大,需要定義一個函數bijiao()來判斷兩個高精度數大小,用一個全局布爾型變量fu存儲大小情況。被減數小,fu=1,否則fu=0。后面的高精減函數則根據fu的值來確定減法策略,fu=0時b-a否則是a-b,輸出函數則根據fu情況來確定是否輸出負號。
判斷兩數大小函數:
bool bijiao()
{
    int ai,bi;
    ai=a[0];
    while(a[ai]==0)a[ai--];//為了讓大小比較更准確,都去掉前導0 
    bi=b[0];
    while(b[bi]==0)b[bi--];//為了讓大小比較更准確,都去掉前導0
    if(ai<bi)return 0;       //如果被減數的位數比減數小,返回0
    else if (ai>bi)retrun 1; //如果被減數的位數比減數大,返回1
    else     //兩者位數相等則從高位開始逐位判斷。
    {
        for(int j=ai;j>0;j--)
            if(a[j]<b[j])return 0;       
            else if(a[j])>b[j])return 1;
        return 1;        //兩個數完全相等
     }
}
兩數相減函數
void gjjian()
{
    int i,c,k=0,t;
    if(fu==1)
    {
         c=a[0];
        for(i=1;i<=c;i++)
        {
            if(a[i]+k>=b[i]){a[i]=a[i]+k-b[i];k=0;}
              else  {a[i]=a[i]+10+k-b[i];k=-1;}
        }   
    }
    else
    {
         c=b[0];
        for(i=1;i<=c;i++)
        {
            if(b[i]+k>=a[i]){a[i]=b[i]+k-a[i];k=0;}
             else{a[i]=b[i]+10+k-a[i]; k=-1;}
        }   
    }
}
輸出函數
void shuchu()
{
    int i=1000;
    if(fu==0)cout<<"-";
    while(a[i]==0)i--;
    while(i>0)cout<<a[i--];
    cout<<endl;
    return;
}
(六)、考慮有正負號的高精度加、減法運算。
只需要在接收數據時判斷接收的數字是否是負數,再在運算中設計相應的運算策略即可,例如:a(+)+b(-)轉化成a-b等,不再詳細說明。
(七)、高精乘法運算。
完成高精乘法運算的話只需列一個豎式的乘法運算就能輕松完成程序設計。找到其中的關鍵規律:個位和個位相乘,結果保存在個位,個位和十位相乘,結果保存在十位,十位和十位相乘結果保存在百位,依次類推可以得出遞推公式,第i位的數和第j位數相除,結果保存在第(i+j-1)位。
所以程序只需再定一個數組c來保存a和b各位相乘的結果,最后統一完成各位的進位就可以了,相乘部分函數代碼如下
void gjchen()
{
    int i,j;
    for(i=1;i<=a[0];i++)
        for(j=1;j<=b[0];j++)
{
c[j+i-1] = c[j+i-1] + a[i]*b[j];
c[i+j]=c[i+j]+c[i+j-1]/10;
c[i+j-1]=c[i+j-1]%10;
           }
}
(八)、高精度除法。
除法運算和前面所講的高精加,高精減以及高精除不一樣,高精除是從高位開始除(不理解的話,請麻煩列一個豎式除法運算)。所有高精除的數據存儲要正序存放,最高位在最左邊(切記!切記!)。
同樣,想要理解高精除法運算的原理,請默默的列一個豎式除法運算。
先看看高精度數除以低精度數。
設參與運算的兩個數分別為a和b,以字符串的方式讀入數a,並將a中的每一位數字分別存儲在a數組中。由於b是低精度數,可以用一般的整數變量來接收和存儲。
運算過程:由於除數是低精,所以我們完全可以定義一個整型變量t來完成除法運算,同時定義一個整型數組來存儲商。第一輪算是將被除數的最高位a[1]賦值給t,將t/b的商存儲在c[1](c[1]=t/b),並將余數存儲在t(t=t%b)。第2輪運算就是將上輪運算的余數乘10再加上本輪補除數位數上的數字t=t*10+a[2],將t/b的商存儲在c[2](c[2]=t/b),並將余數存儲在t(t=t%b)。第i輪運算就是將上輪運算的余數乘10再加上本輪補除數位數上的數字t=t*10+a[i],將t/b的商存儲在c[i](c[i]=t/b),並將余數存儲在t(t=t%b)。直到數組的最低位a[a[0]]參與運算。
例 123456 除以5
被除數 1   2   3   4   5   6   
第一步 1                       1除5,商0余1;
第二步 余1  12                  前面的余1*10+2=12,12除5商2余2
        余2  23              前面的余2*10+3=23,23除5商4余3
            余3  34          前面的余3*10+4=34,34除5商6余4
                余4  45      前面的余4*10+5=45,45除5商9余0
                    余0  6   前面的余0*10+6=6,6除5商1余1
商   0   2   4   6   9   1   最后余1
輸出商時要注意兩點:1、去除無效的前導0;2、按位輸出到c數組的最低位(a[0]商的最低位位數和被除數的最低位的位數相同)位為止。
代碼如下:
#include<iostream>
#include<cstring>
using namespace std;
int a[1010];  //被除數 
int b ;       //除數 
int c[1010]; //商
int d;        //余數      
void gjchudj()
{
    c[0]=a[0];
    int i, t=0;
    for(i=1;i<=a[0];i++){t=t*10+a[i];c[i]=t/b;t=t%b;}
    d=t;
}
void shuchu()
{
    int i=1;
    while(c[i]==0)i++;
    while(i<=c[0])cout<<c[i++];
    cout<<endl<<d<<endl;
   return;
}
void dh()
{
    int an,bn,i,t,d;
    char ac[1010];
    cin>>ac>>b;
    d=strlen(ac);//a高精度數的位數存在a[0]中,要注意,如果輸入的
t=0;        //數字有前導0的話,需要先清除前導0,代碼如下: 
    while(ac[t]=='0')t++;
    a[0]=d-t;
    for(i=t;i<=d;i++)a[i-t+1]=ac[i]-'0';
}
int main()
{
    dh();
    gjchudj();
    shuchu();
    return 0;
}
高精度數除以高精度數。
采用字符串讀入的方式接收和存儲數據,設參與運算的兩個數分別為A和B,並將相應的字符串轉化為數值,將A、B中的每一位數字分別存儲在a和b數組中。
運算思路:用減法代替除法運算。
下面以12345678(存在數組a中)除以11111組b中)為例模擬整個運算過程(用c數組存商)。
第一步:用數組存確定商的最大位數。c[0]=a[0]-b[0]+1;即c[0]=4;
第二步:循環減數i從c[0]開始依次遞減循環
步驟1、定義一個中間數組tt用來保存,將tt的前i-1位都賦值為0,從第i位開始將b的數值賦值給tt
當i=4的時候,tt數組的狀態如下表
存儲位 0   1   2   3   4   5   6   7   8   
a   8   8   7   6   5   4   3   2   1   當i=4的時候將b數組所有的數據右移3(i-1)格,並存儲在tt數組中。
b   5   5   4   3   2   1               
tt  8   0   0   0   1   1   1   1   1   
該部分子函數代碼如下:(p是數b所在數組,q是數組tt)
void fuzhi(int p[],int q[],int wei)
{
    int i;
for(int i=1;i<wei;i++)q[i]=0;
    for(i=wei;i<p[0]+wei;i++)q[i]=p[i-wei+1]
    q[0]=p[0]+wei-1;
}
步驟2、比較數組a和tt的大小。比較方法,先判斷兩個數的位數,a的位數多則返回1,tt的位數多則返回-1;如果位數相同則從高位開始逐位判斷大小,只要在某一位上出現大小不等的情況那么直接返回相應的比較結果(a[i]>b[i]返回1;a[i]<b[i]返回-1),如果所有位數都相同則返回0。
該部分子函數代碼如下:(p是數a所在數組,q是數組tt)
int bijiao(int a[],int b[])
{
    int i;
if(a[0]>b[0])return 1;
if(a[0]<b[0])return -1;
for(i=a[0];i>=1;i--)
{
    if(a[i]>b[i])return 1;
    if(a[i]<b[i])return -1;
}
    return 0;
}
步驟3、根據比較的結果執行相應的運算:
如果返回值是1則說明a比tt大,則先將c[i]++,然后a減去tt,減完后去除a的前導零,並重新確定a數組的准確位數。回到前面的步驟2重新判斷a和tt的大小。
如果返回的值是0則說明a已經能被b所整除,那么c[i]++;后結束除法。
如果返回的值是 -1 則說明a比tt小,那么i--;再回到步驟1繼續。
反復循環直到i=0結束循環。

整個過程列表模擬如下:
存儲位 0   1   2   3   4   5   6   7   8   
a   8   8   7   6   5   4   3   2   1   當i=4的時候將b數組所有的數據右移3(i-1)格,並存儲在tt數組中。判斷a和tt的大小。
tt  8   0   0   0   1   1   1   1   1   
減后結果a   8   8   7   6   4   3   2   1   0   a>tt則a-tt,(從低位開始)c[4]+1=1
新a  7   8   7   6   4   3   2   1       去除前導零重新確定a的位數。比較a和tt的大小
原tt(i=4)    8   0   0   0   1   1   1   1   1   
新tt(i=3)    7   0   0   1   1   1   1   1       a<tt則i-1(=3)重新賦值tt, 比較a和tt的大小
減后結果a   7   8   7   5   3   2   1   0       a>tt則a-tt(從低位開始),c[3]+1=1
新a  6   8   7   5   3   2   1           去除前導零重新確定a的位數。比較a和tt的大小
tt(i=3) 7   0   0   1   1   1   1   1       
新tt(i=2)    6   0   1   1   1   1   1           a<tt則i-1(=2)重新賦值tt, 比較a和tt的大小
減后結果a   6   8   6   4   2   1   0           a>tt則a-tt(從低位開始),c[2]+1=1
新a  5   8   6   4   2   1               去除前導零重新確定a的位數。比較a和tt的大小
tt(i=2) 6   0   1   1   1   1   1           
新tt(i=1)    6   1   1   1   1   1               a<tt則i-1(=1)重新賦值tt, 比較a和tt的大小
減后結果a   5   7   5   3   1   0               a>tt則a-tt(從低位開始),c[2]+1=1
新a  4   7   5   3   1                   去除前導零重新確定a的位數。比較a和tt的大小
tt(i=1) 6   1   1   1   1   1               a<tt則i-1(=0)結束除法。
最后c 4   1   1   1   1                   去除前導零,輸出商1111
最后a 4   7   5   3   1                   去除前導零,輸出余數1357
整個高精除高精的除法完整代碼如下:
#include <bits/stdc++.h>
using namespace std;
int a[101],b[101],c[101],d,i;
void shuru(int a[])
{
char s[101];cin>>s;  //讀入字符串
    a[0]=strlen(s);      //a[0]儲存字符串的長度
    for (i=1;i<=a[0];i++)
        a[i]=s[a[0]-i]-'0'; //將字符串轉化為數組a,並倒序儲存
}
void shuchu(int a[])// 用於輸出最后的答案,並注意若答案為0的情況
{
    int i;
    if (a[0]==0) {cout<<"0"<<endl;return;}
    for (i=a[0];i>0;i--) cout<<a[i];
    cout<<endl;
    return;
}
int bijiao(int a[],int b[])//比較a和b的大小關系,若a>b則為1,若a<b則為-1,若a=b則為0
{
    int i;
    if (a[0]>b[0]) return 1;  //若a的位數大於b,則a>b
    if (a[0]<b[0]) return -1;  //若a的位數小於b,則a<b
    for (i=a[0];i>0;i--)//從高位到低位依次比較,找出大小關系
{
if (a[i]>b[i]) return 1;
if (a[i]<b[i]) return -1;
} 
    return 0;
}
void jian(int a[],int b[])  //a數組既做被除數,又作為儲存余數
{
    int pd,i;
    pd=bijiao(a,b);            //調用函數比較ab大小
    if (pd==0) {a[0]=0;return;}  //相等
    if (pd==1)
    {
        for (i=1;i<=a[0];i++)
        {
            if (a[i]<b[i]) {a[i+1]--;a[i]+=10;} //若不夠減上借一位
            if (a[i]>=b[i]) a[i]-=b[i];
        }
        while((a[a[0]]==0)&&(a[0]>0)) a[0]--;
        return;
    }
}
void numcpy(int p[],int q[],int det) //復制p到q從wei開始的地方
{
    for (int i=1;i<=p[0];i++) q[i+det-1]=p[i];//將數組右移,使兩個數組右端對齊,形參q數組儲存右移后的結果
    q[0]=p[0]+det-1;
}
void chugao(int a[],int b[],int c[])
{
    int i,tmp[101];
    c[0]=a[0]-b[0]+1;
    for (i=c[0];i>0;i--)
    {
        memset(tmp,0,sizeof(tmp));     //tmp數組清零
        numcpy(b,tmp,i);               //將除數b右移后復制給tmp數組,注意用i控制除數位數
        while (bijiao(a,tmp)>=0){c[i]++;jian(a,tmp);}   //減法模擬除法,並計數
    }
    while((c[c[0]]==0)&&(c[0]>0)) c[0]--;   // 控制最高位的0
}
int main()//主程序
{
    memset(a,0,sizeof(a));
    memset(b,0,sizeof(b));
    memset(c,0,sizeof(c));
    shuru(a);shuru(b);
    chugao(a,b,c);
    shuchu(c);
    shuchu(a);
    return 0;
}


免責聲明!

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



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