关于高精度

关于高精度

                                                                                                  作者:焦祺         08-11-8

 

所谓的高精度运算,是指参与运算的数(加数,减数,因子……)范围大大超出了标准数据类型(整型,实型)能表示的范围的运算。

高精度的特征使我们在运用上一般会借助函数模板,不过在有的情况是需要我们更改的,所以高精度的计算还是值得我们深入研究。

 

高精度计算的一般分类:

l  高精度加法

l  高精度减法

l  高精度乘法(阶乘)

l  高精度除法(求余)

l  高精度数比较

 

 

高精度加法计算原理:

观察人工计算的方法:竖式加法

       223

+     996

-----------

         9       3+6=9

        1          2+9=11,当前位1,进位1

       2            2+9+1=12,当前位2,进位1

  1               进位的1

-----------

   1219      

 

 

算法框架:

AAN位数,BBN位数,结果存到C

N=AN>=BN?AN:BN;//求得两数的高位

for(从个位开始 TO 两数最高位N)

{

        C的第i = A的第i + B的第i

        进位位 = Ci / 10

        当前位的值 = Ci %10

}


 

自编高精度加法函数模板:

 

/**

 * @函数名       : add

 * @brief     : 高精度加法计算

 * @return   : void

 * @param   : char ha[]

 * @param   : char hb[]

 * @param   : char answer[]

 * @remark : 结果返回到answer[]

*/

#define M 500

void add(char ha[],char hb[],char answer[])

{

       int a[M],b[M],c[M];

       int la,lb,len,i;

       memset(a,0,sizeof(a));

       memset(b,0,sizeof(b));

       memset(c,0,sizeof(c));

       la=strlen(ha);lb=strlen(hb);       //求串长

       len = la>=lb?la:lb;                           //求两个串长的最长者

      

       for(i=0; i<la; i++)                            //将字符串a1逆序存到整型数组a

              a[i] = ha[la-i-1]-'0';

      

       for(i=0; i<lb; i++)                            //将字符串b1逆序存到整型数组b

              b[i] = hb[lb-i-1]-'0';

 

       for(i=0; i<len ;i++)                   //数字处理

       {

              c[i] += a[i]+b[i];                //处理a+b

              c[i+1] += c[i]/10;               //处理进位数

              c[i] = c[i]%10;                          //处理原位数

       }

//     outputadd(c,len);                            //直接输出结果

       if (c[len] == 0)

              len--;

       for (i=0; i<=len; i++)

              answer[i] = c[len-i]+'0';     //把结果存到answer[]

       answer[i] = ‘/ 0’ ;

}

 


 

加法输出函数:

 

/**

 * @函数名       : output

 * @brief     : 高精度加法专用输出

 * @return   : void

 * @param   : int res[]       输出的结果

 * @param   : int len  结果的长度

 * @remark : 注意第一位是否为0,len包含前导零可能

*/

void outputadd(int res[],int len)

{

       int i;

       for(i=len;i>=0;i--)

       {

              if (res[i] == 0 && i==len)

              {

                     continue;

              }

              printf("%d",res[i]);

       }

       printf("/n");

}

 

高精度加法计算的时间效率提高方法:

 

在运用中,加法的高精计算算是比较简单,用起来也比较那灵活多变一点,在简单的题目中可以用二维数组计算打表。这样不用每次都要去调用高精度加法函数,从而可以提高实战中的时间效率。

 

如:HDOJ1715 ( 大菲波数 )

 

#include<iostream>

#include<string.h>

using namespace std;

int a[1005][220];   

int main()

{

    int j,i,k,l,t,n,m,flag;   

    memset(a,0,sizeof(a));

    a[1][0] = 1;

    a[2][0] = 1;

    for(i=3;i<=1000;i++)

        for(j=0;j<220;j++)

        {

            a[i][j] += a[i-1][j]+a[i-2][j];    //

            if(a[i][j] > 9)

            {

                a[i][j] = a[i][j] % 10;        //进位

                a[i][j+1]+=1;

            }

        }

        //-----------------------------------------------------------------

        scanf("%d",&n) != EOF;

        while(n--)

        {

            scanf("%d",&m);    //m为要求位

            //----------------------------output:

            for(j=220-1 ; j>=0 ;j--)                    //找出最高位

                if(a[m][j] != 0)

                {

                    flag = j;

                    break;

                }

                for(j=flag ;j>=0;j--)

                    printf("%d",a[m][j]);

                printf("/n");

        }

        return 0;

 

以上的方法可以较好的解决时间效率的问题,但在有些运用中,空间效率却成为考点,由于计算高精度常用INT数组,所以优化时就要在数组上下功夫了。

如:HDOJ1250 ( Hat's Fibonacci )

 

#include<iostream>

using namespace std;

int num[7038][260]={0};

int main()

{

    int i,j,n;

    num[1][0]=1;

    num[2][0]=1;

    num[3][0]=1;

    num[4][0]=1;

    for(i=5;i<7039;i++)

    {   

        for(j=0;j<260;j++)

            num[i][j]=num[i-1][j]+num[i-2][j]+num[i-3][j]+num[i-4][j];//公式

        for(j=0;j<259;j++)

            if(num[i][j]>100000000)

            {

                num[i][j+1]+=num[i][j]/100000000;

                num[i][j]%=100000000;

            }

    }

    while(scanf("%d",&n)!=EOF)

    {

        for(i=259;i>0;i--)

            if(num[n][i]!=0)

                break;

        printf("%d",num[n][i]);

        for(j=i-1;j>=0;j--)

            printf("%08d",num[n][j]); //上面有8个0,注意这里的输出控制

        printf("/n");

    }

    return 0;

}

 

高精度减法计算原理:

 

竖式减法:

       927

-      896

-----------

         1        7-6=1

        3          12-9=3,借位12

       0            (9-1)-8=0

-----------

         31

 

注意:

  1. 注意减法的特性:借位
  2. 不要忽略连续借位!
  3. 借位前被借位是0的情况
  4. 注意可能出现负数!
  5. 前导零处理

 

 

 

 

算法框架:

 

for(从个位开始 TO 两数最高位N)

{

              if(a的第i位小于b的第i)     

              {//那么需要借位

                     借位位减1

                     当前位加10

              }

       ai位和bi位的差存入a;

}

 

自编高精度减法函数模板:

 

/**

 * @函数名       : subtract

 * @brief     : 高精度减法

 * @return   : void

 * @param   : char ha[]     被减数

 * @param   : char hb[]     减数

 * @param   : char answer[]    

 * @remark : 结果返回到answer[]

*/

#define M 500

void subtract(char ha[],char hb[],char answer[]) 

{

       int a[M],b[M];

       int la,lb,len,i;

       memset(a,0,sizeof(a));

       memset(b,0,sizeof(b));

       la=strlen(ha);lb=strlen(hb);       //求串长

       len = la>=lb?la:lb;                           //求两个串长的最长者

       for(i=0; i<la; i++)                            //将字符串ha逆序存到整型数组a

              a[i] = ha[la-i-1]-'0';

       for(i=0; i<lb; i++)                            //将字符串hb逆序存到整型数组b

              b[i] = hb[lb-i-1]-'0';

       if(compare(a,la,b,lb))                              //如果是正数

       {

              for(i=0;i<la;i++)                //处理高精度减法

              {

                     if(a[i] < b[i])

                     {

                            a[i+1]--;               //借位

                            a[i]+=10;                    //借位后处理当前位加10

                     }

                     a[i] = a[i] - b[i];           //处理后存至a

              }

              while(!a[la-1])                          //去掉前导零

              {

                     la--;

                     if(!la)                                 //如果全是零,则只输出一个零

                     {

                            answer[0] = '0';

                            answer[1] = '/0';

                            return ;

                     }

              }

              for(i=0; i<la; i++)              //从非零位开始输出

                     answer[i] = a[la-i-1] + '0';

       }

       else                                           //如果是负数

       {

              for(i=0;i<lb;i++)                //处理高精度减法

              {

                     if(b[i] < a[i])

                     {

                            b[i+1]--;               //借位

                            b[i]+=10;                    //借位后处理当前位加10

                     }

                     b[i] = b[i] - a[i];          //处理后存至b

              }

              while(!b[lb-1])

              {

                     lb--;

                     if(!lb)

                     {

                            answer[0] = '0';

                            answer[1] = '/0';

                            return ;

                     }

              }

              answer[0] = '-';

              for(i=1; i<lb+1; i++)          //从非零位开始输出

                     answer[i] = b[lb-i] + '0';    

       }

}

 

 

 

比较函数:

 

/**

 * @函数名       : compare

 * @brief     : 判断哪个数大

 * @return   : bool

 * @remark : 正数返回1,负数返回0

*/

bool compare(int a[],int la,int b[],int lb)

{

       int i;

       if(la>lb)        //被减数位数 大于 减数位数,差一定是正数

       {

              return 1;

       }

       else if(la<lb) //被减数位数 小于 减数位数,差一定是负数

       {

              return 0;

       }

       else //la == lb 结果可能是正也可能是负

       {

              for (i=0;i<la;i++) //从最高位开始判断,看哪个最高位先大

              {

                     if(a[i] > b[i])

                     {

                            return 1;

                     }

                     else if (a[i] < b[i])

                     {

                            return 0;

                     }

              }

              if(i == la)

              {

                     return 1;

              }

       }

}

 


高精度乘法计算原理:

 

竖式乘法:

1. 大数乘小数,比如12345*9=?

 1 2 3 4 5

*          9

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

           45           5*9=45

    36                4*9=36

  27                    3*9=27

18                        2*9=18

+     9                          1*9=9

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

1 1 1 1 0 5

 

2. 大数乘大数,比如12345*12345=?

12345*12345=12345*5+

               12345*4*10+

               12345*3*100+

               12345*2*1000+

               12345*1*10000

问题转化为若干个大数乘小数之和

 

算法框架

用一个i,j二重循环{{res[i+j]+=b[i]*a[j];}}

//不管三七二十一先把结果算出来

//然后我们再来考虑进位:

for(……)

{

如果:res[i] >= 10

那么:

res[i+1] += res[i]/10; //进位位

res[i] %= 10;        //原始位

}

 

自编高精度乘法函数模板:

 

/**

 * @函数名 : multiply

 * @brief      : 高精度乘法计算

 * @return    : void

 * @param    : char ha[]

 * @param    : char hb[]

 * @param    : char answer[]

 * @remark  : 结果返回到answer[]

*/

#define M 500

void multiply(char ha[],char hb[],char answer[])

{

int          i,j;

int          la;                        //被乘数位数

int          lb;                        //乘数位数

int          a[M+10];             //存被乘数

int          b[M+10];             //存乘数

int          res[2*M+10];      //存结果

memset(answer,NULL,sizeof(answer));

memset(res,0,sizeof(res));

la = strlen(ha);

lb = strlen(hb);

for(i=0; i<la; i++)                     //将字符串a1逆序存到整型数组a

        a[i] = ha[la-i-1]-'0';

for(i=0; i<lb; i++)                     //将字符串b1逆序存到整型数组b

        b[i] = hb[lb-i-1]-'0';

for (i=0;i<lb;i++)               //每次用b的一位,去和a的各位相乘

{

        for (j=0;j<la;j++)

        {

               res[i+j]+=b[i]*a[j];

        }

}

for (i=0;i<M*2;i++)                        //处理进位

{

        if (res[i] >= 10)

        {

               res[i+1] += res[i]/10;  //进位位

               res[i] %= 10;               //原始位

        }

}

//output();

bool flag = 0;j=0;

for(i=M*2; i>=0 ;i--)

{

        if(flag)

        {

               answer[j] = res[i] + '0';

               j++;

        }

        else if (res[i])              //不存入前导零

        {

               answer[j] = res[i] + '0';

               j++;

               flag = 1;

        }

}

if (!flag)

{

        answer[0] = '0';

        j++;

}

answer[j] = '/0';

}

 

乘法输出函数:

 

/**

 * @函数名 : output

 * @brief      : 输出积

 * @return    : void

 * @remark  : 注意前导零

*/

void output(int res[])

{

bool flag = 0;

int  i;

for(i=M*2; i>=0 ;i--)

{

        if(flag)

        {

               printf("%d",res[i]);

        }

        else if (res[i])              //不输出前导零

        {

               printf("%d",res[i]);

               flag = 1;

        }

}

if (!flag)

{

        printf("0");

}

printf("/n");

}

 

高精度除法计算原理:

 

竖式除法----用多次的减法来实现!

             36

12345 452678

         37035      12345*3=37035

          8232        45267-37035=8232

          82328      下一位再次减法

          74070      12345*6=74070

           8258      82328-74070=8258 余数

 

高精度除以低精度算法框架

d = 0

for(从除数的最高位 to 除数的个位)

{

       d += a的第i

       计算d和除数的商 存入C数组中

       计算d和除数的余数 存入d

     处理该位的余数便是下一位的十位

}

 

自编高精度除以低精度函数模板:

 

/**

 * @函数名 : divide

 * @brief      : 高精度除以低精度函数模板

 * @return    : int

 * @param    : char ha[]

 * @param    : int b

 * @param    : char answer[]

 * @remark  : 结果返回到answer[]

*/

int divide(char ha[],int b,char answer[])

{

int          i,la,j;

int          d;                  //余数

int          c[N]={0};     //

int          a[N]={0};     //被除数

if (b == 0)

{

        return -1;

}

la=strlen(ha);

memset(a,0,sizeof(a));

memset(answer,NULL,sizeof(answer));

for(i=0;i<la;i++)

        a[i] = ha[la-i-1]-'0';

d=0;

for(i=la-1;i>=0;i--)

{

        d = d*10 + a[i];   //a的最高位开始

        c[i] = d/b;                   //把商一位一位存入C

        d=d%b;                      //该位的余数便是下一位的十位数

}  

while(c[la-1]==0 && la>1)

        la--;

//      输出结果

//     for(i=la-1; i>=0; i--)

//            printf("%d",c[i]);

j=0;

for (i=la-1;i>=0;i--)

{

        answer[j] = c[i]+'0';

        j++;

}

answer[j] = '/0';   

return d;

}

 

高精度除以高精度算法框架

d=0;//余数

for(从除数的最高位 to 除数的个位)

{

     d自乘10     //高精度乘10

     被除数的第i位加入d

     while(d大于除数)

{

d和除数的差存在d

       ++;

}

 }

 

高精度除法函数模板:

 

#define N 1001

int compare(char * c1,int s1,int e1,char *c2,int s2,int e2)/*大整数比较大小*/

{

while(c1[s1]=='0'&&s1<e1)s1++;

while(c2[s2]=='0'&&s2<e2)s2++;

if(e1-s1>e2-s2)return 1;

if(e1-s1<e2-s2)return -1;

int i1;int f11=0;

for(i1=0;i1<=e1-s1;i1++)

{

        if(c1[s1+i1]>c2[s2+i1]){f11=1;break;}

        if(c2[s2+i1]>c1[s1+i1])break;

}

if(i1>e1-s1)return 0;

if(f11)return 1;

return -1;

}

void sub(char *c1,int l1,char *c2,int n,int l)/*两个大整数相减,结果放在c1*/

{

int fig=0;

if(l1>l)

{

        fig=1;

        int i5;

        for(i5=l;i5>0;i5--)

               c2[i5]=c2[i5-1];

        c2[0]='0';l++;

}

int jw=0;

int i4;

char tc[N];

for(i4=l-1;i4>=0;i4--)

{

        tc[i4]=((c2[i4]-48)*n+jw)%10+48;

        jw=((c2[i4]-48)*n+jw)/10;

}

jw=0;

for(i4=l-1;i4>=0;i4--)

{

        int ttt=jw;

        if(c1[i4]-tc[i4]-jw>=0)

        {

               tc[i4]=c1[i4]-tc[i4]-jw+48;

               jw=0;

        }

        else

        {

               jw=(tc[i4]-c1[i4]+jw);

               if(jw%10==0)jw/=10;

               else jw=jw/10+1;

               tc[i4]=jw*10+c1[i4]-tc[i4]-ttt+48;

        }

}

if(fig)

{

        for(i4=0;i4<l-1;i4++)c2[i4]=c2[i4+1];

        l--;

        c2[l]='/0';

}

tc[l1]='/0';

for(i4=0;i4<=l1;i4++)

        c1[i4]=tc[i4];

}            

void high_precise_division(char *c1,char *c2)

{

int len1,len2;

len1=strlen(c1);

len2=strlen(c2);

int i,j,k,ip;

i=0;

while(c1[i]=='0'&&i<len1-1)i++;

for(j=0;i<len1;j++,i++)c1[j]=c1[i];

len1=j;c1[j]='/0';

i=0;

while(c2[i]=='0'&&i<len2-1)i++;

for(j=0;i<len2;j++,i++)c2[j]=c2[i];

len2=j;c2[j]='/0';

/*while(c1[0]=='0'&&i<len1-1)

{

        for(k=0;k<len1-1;k++)

               c1[k]=c1[k+1];

        len1--;

}去掉前面的0*/

/*while(c2[0]=='0'&&j<len2-1)

{

        for(k=0;k<len2-1;k++)

               c2[k]=c2[k+1];

        len2--;

}去掉前面的0*/

c1[len1]='/0';

c2[len2]='/0';

if(strcmp(c2,"0")==0)return ;/*当除数为0*/

if(strcmp(c1,"0")==0){strcpy(c2,"0");return ;}/*当被除数为0*/

if(len1<len2||len1==len2&&compare(c1,0,len1-1,c2,0,len2-1)<0)

{

        strcpy(c2,c1);

        c1[0]='0';c1[1]='/0';

        return ;

}

else

{

        ip=0;

        char product[N],*pr;/*部分积*/

        pr=product;

        for(ip=0;ip<len2-1;ip++)

               pr[ip]=c1[ip];

        for(i=0;i<=len1-len2;i++)

        {

               pr[ip++]=c1[len2-1+i];

               if(ip>=len2 && compare(pr,0,ip-1,c2,0,len2-1)>=0)

               {

                      char tc[N];

                      for(j=1;j<=9;j++)

                      {

                             for(k=0;k<ip;k++)tc[k]=pr[k];

                             sub(tc,ip,c2,j,len2);/*pr-c2*j结果放在pr*/

                             for(k=0;tc[k]!='/0';k++);

                             if(compare(tc,0,k-1,c2,0,len2-1)<0)

                                    break;

                      }

                      strcpy(pr,tc);

                      ip=strlen(pr);

                      c1[i]=j+48;

                      while(pr[0]=='0'&&ip>1)

                      {

                             for(j=0;j<ip-1;j++)

                                    pr[j]=pr[j+1];

                             ip--;

                      }

                      if(ip==1&&pr[0]=='0')ip--;

               }

               else c1[i]='0';

        }

        while(c1[0]=='0')

        {

               for(j=0;j<i-1;j++)

                      c1[j]=c1[j+1];

               i--;

        }

        c1[i]='/0';

        if(ip==0){pr[0]='0';pr[1]='/0';ip=1;}

        else

        {

               while(pr[0]=='0'&&ip>1)

               {

                      for(j=0;j<ip-1;j++)pr[j]=pr[j+1];

                     ip--;

               }

        }

        for(j=0;j<ip;j++)

               c2[j]=pr[j];

        c2[ip]='/0';

        return ;

}

}

int main()

{

int i,j;

char c1[N],c2[N];/*整数高精度除法*/

while(scanf("%s %s",c1,c2)!=EOF)

{

        char *pc1,*pc2;

        pc1=c1;pc2=c2;

        c1[strlen(c1)]='/0';

        c2[strlen(c2)]='/0';

        high_precise_division(pc1,pc2);/*结果放在pC1中,余数放在pC2*/              

        printf("  :%s/n余数:%s/n",pc1,pc2);

}

return 0;

}

 

 

高精度阶乘函数模板:

 

语法:int result=factorial(int n);

参数:

nn 的阶乘

返回值:阶乘结果的位数

注意:

本程序直接输出n!的结果,需要返回结果请保留long a[]

需要 math.h

源程序:

int factorial(int n)

{

       long a[10000];

       int i,j,l,c,m=0,w;

       a[0]=1;

       for(i=1;i<=n;i++)

       {

              c=0;

              for(j=0;j<=m;j++)

              {

                     a[j]=a[j]*i+c;

                     c=a[j]/10000;

                     a[j]=a[j]%10000;

              }

              if(c>0) {m++;a[m]=c;}

       }

      

       w=m*4+log10((double)a[m])+1;

       printf("%ld",a[m]);

       for(i=m-1;i>=0;i--) printf("%4.4ld",a[i]);

       return w;

}

 

 

PKU上关于高精度的题目汇集:

1001(高精度乘法) 2413(高精度加法,还有二分查找)

1220, 1405, 1503, 1131, 2305,2325,2389,1604,1047 

1504 1517 1519 1547 1552 1563(考虑仔细一点,还要注意精度)

2381 2385 2393 2394 2395 2413(高精度基础) //2418 2419

 

 

参考文献:

 

《程序设计基础第2版》吴文虎 清华大学出版社

ACM程序设计培训教程》      中国铁道出版社

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值