1. 程式人生 > >高精度演算法(C/C++)

高精度演算法(C/C++)

高精度演算法 (C/C++)

做ACM題的時候,經常遇到大數的加減乘除,乘冪,階乘的計算,這時給定的資料型別往往不夠表示最後結果,這時就需要用到高精度演算法。高精度演算法的本質是把大數拆成若干固定長度的塊,然後對每一塊進行相應的運算。這裡以考慮4位數字為一塊為例,且輸入的大數均為正整數(也可以考慮其他位,但要注意在每一塊進行相應運算時不能超出資料型別的數值範圍;有負整數的話讀入時判斷一下正負號在決定運算)。

1. 高精度加法

以3479957928375817 + 897259321544245為例:

3479 9579 2837 5817
+897 +2593 +2154 +4245
= = = =
4376 12172 4991 10062
進位0 進位1 進位0 進位1
4377 2172 4992 0062

C語言實現程式碼如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 200

//整數乘冪運算函式
int Pow(int a, int b)
{
    int i = 0, result = 1;
    for(i = 0; i < b; ++i)
    {
        result *= a;
    }
    return result;
}


//High Precision Of Addition
int main()
{
    char stra[N], strb[N];      //字串陣列,以字元形式儲存兩個大數;
    int i = 0, step = 4, carry = 0; //step表示塊長,carry為進位位;
    int lengtha, lengthb, maxlength, resultsize;    //maxlength表示stra和strb二者長度較大的那個;
    int numa[N], numb[N],numc[N];   //依次儲存被加數,加數,和;
    memset(numa, 0, sizeof(numa));
    memset(numb, 0, sizeof(numb));
    memset(numc, 0, sizeof(numc));         //初始化為零;
    scanf("%s%s", stra, strb);
    lengtha = strlen(stra);
    lengthb = strlen(strb);     //計算兩個大數的長度
    //字元數字轉為四位一塊的整數數字
    for(i = lengtha-1; i >= 0; --i)
    {
        numa[(lengtha-1-i)/step] += (stra[i]-'0')*Pow(10,(lengtha-1-i)%step);
    }
    for(i = lengthb-1; i >= 0; --i)
    {
        numb[(lengthb-1-i)/step] += (strb[i]-'0')*Pow(10,(lengthb-1-i)%step);
    }
    maxlength = lengtha > lengthb ? lengtha : lengthb;

    //逐塊相加,並進位
    for(i = 0; i <= maxlength/step; ++i)
    {
        numc[i] = (numa[i] + numb[i])%Pow(10, step) + carry;    //計算和
        carry = (numa[i] + numb[i])/Pow(10, step);  //計算進位
    }

    //計算最後和的塊的總數
    resultsize = numc[maxlength/step] > 0 ? maxlength/step : maxlength/step - 1;
    printf("%d", numc[resultsize]);
    for(i = resultsize-1; i >= 0; --i)
    {
        printf("%04d", numc[i]);    //右對齊,補零輸出;
    }
    printf("\n");
    return 0;
}

2. 高精度減法

與加法類似,不同的是要注意正負號和顯示位數的變化。以99999037289799 - 100004642015000為例:
先判斷被減數和減數哪個大,顯然這裡減數大,故輸出結果為負數。在用大數減去小數,(若某一塊相減為負數,則要向高位塊借位)如下表

100 0046 4201 5000
-99 -9990 -3728 -9799
1 56 473 5201
借位0 借位1 借位0 借位1
0 56 472 5201

C語言實現程式碼如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 200

//整數乘冪運算函式
int Pow(int a, int b)
{
    int i = 0, result = 1;
    for(i = 0; i < b; ++i)
    {
        result *= a;
    }
    return result;
}

//High Precision Of Subtraction
int main()
{
    char stra[N], strb[N];     //字串陣列,以字元形式儲存兩個大數;
    int i = 0, step = 4, borrow = 0, mark = 0; //step表示塊長,borrow為借位位, mark為結果符號位;
    int lengtha, lengthb, maxlength, resultsize;    //maxlength表示stra和strb二者長度較大的那個;
    int numa[N], numb[N],numc[N],  *maxnum, *minnum;   //依次儲存被減數,減數,和;
    memset(stra, 0, sizeof(stra));
    memset(strb, 0, sizeof(strb));
    memset(numa, 0, sizeof(numa));
    memset(numb, 0, sizeof(numb));
    memset(numc, 0, sizeof(numc));         //初始化為零;
    scanf("%s%s", stra, strb);
    lengtha = strlen(stra);
    lengthb = strlen(strb);     //計算兩個大數的長度
    maxlength = lengtha >= lengthb ? lengtha : lengthb;

    //字元數字轉為四位一塊的整數數字
    for(i = lengtha-1; i >= 0; --i)
    {
        numa[(lengtha-1-i)/step] += (stra[i]-'0')*Pow(10,(lengtha-1-i)%step);
    }
    for(i = lengthb-1; i >= 0; --i)
    {
        numb[(lengthb-1-i)/step] += (strb[i]-'0')*Pow(10,(lengthb-1-i)%step);
    }

    //找出較大的數
    maxnum = numa;
    minnum = numb;
    mark = 1;
    for(i = (maxlength-1)/step; i >= 0; --i)
    {
        if(numa[i] > numb[i])
        {
            maxnum = numa;
            minnum = numb;
            mark = 1;
            break;
        }
        else if(numa[i] < numb[i])
        {
            maxnum = numb;
            minnum = numa;
            mark = -1;
            break;
        }
    }

    //逐塊相減,並借位
    for(i = 0; i <= maxlength/step; ++i)
    {
        numc[i] = (maxnum[i] - minnum[i] + Pow(10, step) + borrow)%Pow(10,step);    //計算差
        borrow = (maxnum[i] - minnum[i] + Pow(10, step) + borrow)/Pow(10, step) - 1;  //計算借位
    }

    //計算最後和的塊的總數
    resultsize = maxlength/step;
    while(!numc[resultsize])    --resultsize;
    printf("%d", mark*numc[resultsize]);
    for(i = resultsize-1; i >= 0; --i)
    {
        printf("%04d", numc[i]);    //右對齊,補零輸出;
    }
    printf("\n");
    return 0;
}

3. 高精度乘法

乘法可以看作是乘數每一位與被乘數相乘後再相加,以4296556241 x 56241為例:

被乘數 42 9655 6241
乘數 5 6 2 4 1
被乘數x乘數 42 9655 6241
1 42 9655 6241
4 168*10 38620*10 24964*10
2 84*100 19310*100 12482*100
6 252*1000 57930*1000 37446*1000
5 210*10000 48275*10000 31205*10000
累加和 2362122 543006855 351000081
進位(從低位向高位) 241 54304 35100
241 6426 1955 0081

C語言實現程式碼如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 200

//整數乘冪運算函式
int Pow(int a, int b)
{
    int i = 0, result = 1;
    for(i = 0; i < b; ++i)
    {
        result *= a;
    }
    return result;
}


//High Precision Of Multiplication
int main()
{
    char stra[N], strb[N];      //字串陣列,以字元形式儲存兩個大數;
    int i = 0, j = 0, k = 0, step = 4, carry = 0; //step表示塊長,carry為進位位;
    int lengtha, lengthb, resultsize, tmpsize, eachnum;  //resultsize儲存塊的總數,eachnum用來儲存乘數的每一位
    int numa[N], numb[N], numc[N], tmp[N];   //依次儲存被乘數數&積,乘數;
    memset(numa, 0, sizeof(numa));
    memset(numb, 0, sizeof(numb));
    memset(numc, 0, sizeof(numc));  //初始化為零;
    scanf("%s%s", stra, strb);
    lengtha = strlen(stra);
    lengthb = strlen(strb);     //計算兩個大數的長度
    //將被乘數字符數字轉為四位一塊的整數數字
    for(i = lengtha-1; i >= 0; --i)
    {
        numa[(lengtha-1-i)/step] += (stra[i]-'0')*Pow(10,(lengtha-1-i)%step);
    }
    //將乘數數字字元數字轉為一位一塊的整數數字
    for(i = lengthb-1; i >= 0; --i)
    {
        numb[lengthb-1-i] = strb[i]-'0';
    }

    resultsize = tmpsize = (lengtha-1)/step;
    //取乘數的每一位與被乘數的逐塊相乘,並進位;
    for(i = 0; i < lengthb; ++i)
    {
        memcpy(tmp, numa, sizeof(numa));    //將numa陣列賦值給tmp陣列;

        k = i/step;     //k儲存每一塊需要向高位塊移動的次數;
        if(k)
        {
            for(j = tmpsize; j >= 0; --j)
            {
                tmp[j+k] = tmp[j];
                tmp[j] = 0;
            }
            tmpsize += k;
        }

        //乘以乘數每一位擴充套件成的塊;
        eachnum = numb[i]*Pow(10, i%step);
        for(j = 0; j <= tmpsize; ++j)
        {
            tmp[j] *= eachnum;
        }

        //大數相加
        carry = 0;  //進位置零;
        for(j = 0; j <= resultsize; ++j)
        {
            numc[j] += tmp[j] + carry;
            carry = numc[j]/Pow(10,step);
            numc[j] %= Pow(10, step);
        }
        if(carry)
        {
            ++resultsize;
            numc[j] += carry;
        }
    }

    //輸出
    printf("%d", numc[resultsize]);
    for(i = resultsize-1; i >= 0; --i)
    {
        printf("%04d", numc[i]);    //右對齊,補零輸出;
    }
    printf("\n");
    return 0;
}

4. 高精度除法

高精度除法有兩種,一種是高精度除以低精度,另一種是高精度除以高精度。前者只需將每一塊除以低精度除數即可;後者則考慮用高精度減法來實現,即每次減去高精度除數,直到減到小於除數,則減的次數即為商,剩餘的即為餘數。

  • 高精度除以低精度
    以9876342876 / 343為例:
被除數 98 7634 2876
除數 343
向低位塊進位 98 137 190
0 2879 4002
餘數 190

C語言程式碼實現如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 200

//整數乘冪運算函式
int Pow(int a, int b)
{
    int i = 0, result = 1;
    for(i = 0; i < b; ++i)
    {
        result *= a;
    }
    return result;
}

//High Precision Of division
//(1)高精度除以低精度
int main()
{
    char stra[N];      //字串陣列,以字元形式儲存高精度被除數;
    int i = 0, step = 4, carry = 0; //step表示塊長,carry為高位向低位進位位;
    int lengtha, resultsize;
    int numa[N], numb, numc[N], numd;   //依次儲存被除數,除數,商, 餘數;
    memset(numa, 0, sizeof(numa));
    memset(numc, 0, sizeof(numc));         //初始化為零;
    scanf("%s%d", stra, &numb);
    lengtha = strlen(stra);   //計算被除數的長度

    //字元數字轉為四位一塊的整數數字
    for(i = lengtha-1; i >= 0; --i)
    {
        numa[(lengtha-1-i)/step] += (stra[i]-'0')*Pow(10,(lengtha-1-i)%step);
    }

    carry = 0;  //高位向低位進位位置零
    resultsize = (lengtha-1)/step;
    //逐塊相除,高位向低位進位
    for(i = resultsize; i >= 0; --i)
    {
        numc[i] = (numa[i] + carry*Pow(10,step))/numb;    //計算商
        carry = (numa[i] + carry*Pow(10,step))%numb;  //計算進位
    }
    numd = carry;   //最低位塊的餘數即為整個除法的餘數

    //計算最後和的塊的總數
    while(!numc[resultsize])    --resultsize;
    //輸出商
    printf("%d", numc[resultsize]);
    for(i = resultsize-1; i >= 0; --i)
    {
        printf("%04d", numc[i]);    //右對齊,補零輸出;
    }
    //輸出餘數
    printf("\n%d\n", numd);
    return 0;
}
  • 高精度除以高精度
    以176342876 / 3453452為例:
被除數 176342876
- (51 x 除數) 51 x 3453452
餘數 216824
51

C語言程式碼實現如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 200

//整數乘冪運算函式
int Pow(int a, int b)
{
    int i = 0, result = 1;
    for(i = 0; i < b; ++i)
    {
        result *= a;
    }
    return result;
}

//High Precision Of division
//(2)高精度除以高精度
int main()
{
    char stra[N], strb[N];     //字串陣列,以字元形式儲存兩個大數;
    int i = 0, step = 4, borrow = 0; //step表示塊長,borrow為進位位;
    int lengtha, lengthb, tmpnum, numbsize, numcsize, numdsize, maxsize, mark;    //maxlength表示stra和strb二者長度較大的那個;
    int numa[N], numb[N], numc[N], numd[N];   //依次儲存被除數數,除數數,商,餘數;
    memset(stra, 0, sizeof(stra));
    memset(strb, 0, sizeof(strb));
    memset(numa, 0, sizeof(numa));
    memset(numb, 0, sizeof(numb));
    memset(numc, 0, sizeof(numc));
    memset(numd, 0, sizeof(numd));      //初始化為零;
    scanf("%s%s", stra, strb);
    lengtha = strlen(stra);
    lengthb = strlen(strb);     //計算兩個大數的長度

    //字元數字轉為四位一塊的整數數字
    for(i = lengtha-1; i >= 0; --i)
    {
        numa[(lengtha-1-i)/step] += (stra[i]-'0')*Pow(10,(lengtha-1-i)%step);
    }
    for(i = lengthb-1; i >= 0; --i)
    {
        numb[(lengthb-1-i)/step] += (strb[i]-'0')*Pow(10,(lengthb-1-i)%step);
    }
    memcpy(numd, numa, sizeof(numa));
    numbsize = (lengthb-1)/step;
    numcsize = 0;
    numdsize = (lengtha-1)/step;

    do
    {
        maxsize = numdsize > numbsize ? numdsize : numbsize;
        //計算剩餘數是否小於除數
        mark = 1;
        for(i = maxsize; i >= 0; --i)
        {
            if(numd[i] > numb[i])
            {
                mark = 1;
                break;
            }
            else if(numd[i] < numb[i])
            {
                mark = -1;
                break;
            }
        }

        //判斷是否餘數已經小於除數
        if(!(mark+1))   break;

        borrow = 0; //借位置零;
        //逐塊相減,並借位
        for(i = 0; i <= maxsize; ++i)
        {
            tmpnum = (numd[i] - numb[i] + Pow(10, step) + borrow)%Pow(10,step);    //計算差
            borrow = (numd[i] - numb[i] + Pow(10, step) + borrow)/Pow(10,step) - 1;  //計算借位
            numd[i] = tmpnum;
        }
        while(!numd[numdsize])  --numdsize;

        //每減一個除數,商加一;
        borrow = 1;
        for(i = 0; i <= numcsize; ++i)
        {
            numc[i] += borrow;
            borrow = numc[i]/Pow(10,step);
            numc[i] %= Pow(10,step);
        }
        if(borrow)
        {
            ++numcsize;
            numc[i] += borrow;
        }
    }while(1);

    printf("%d", numc[numcsize]);
    for(i = numcsize-1; i >= 0; --i)
    {
        printf("%04d", numc[i]);    //右對齊,補零輸出;
    }
    printf("\n");
    printf("%d", numd[numdsize]);
    for(i = numdsize-1; i >= 0; --i)
    {
        printf("%04d", numd[i]);    //右對齊,補零輸出;
    }
    printf("\n");
    return 0;
}