大数问题 大整数运算

简介

对于A+B,如果A和B是有着1000个数位的整数,就不可以用基本的数据类型如int来存储了。这时只可以老实地模拟加减乘除的过程。大整数又称为高精度整数,其含义就是用基本数据类型无法存储其精度的整数。

大整数的存储

我们可以用数组来存储大整数,比如用int型数组d[1000],这个数组的每一位就代表存放整数的每一位。数组的高下标存储的是整数的高位,低下标存储的是整数的低位。

为了方便获取大整数的长度,一般定义一个int型变量len来记录长度,并与d数组构成结构体。

struct bign
{
    int d[1000];
    int len;
};

最好在定义结构体之后马上对其进行初始化,防止后续忘记。可以在结构体中直接设置构造函数。

struct bign
{
    int d[1000];
    int len;
    bign()
    {
        memset(d,0,sizeof(d));
        len=0;
    }
};

在输入大整数的时候,一般都是先用字符串读入,然后再把字符串另存至结构体。注意,要倒着存储。

bign change(char str[])
{
    bign a;
    a.len=strlen(str);
    for(int i=0;i<a.len;i++)
    {
        a.d[i]=str[a.len-i-1]-'0';
        // a.d[i]=str[a.len-i-1]-48;
    }
    return a;
}

比较2个大整数的大小时,先比较2者的len,如果不相等,谁大谁的值就大。如果相等,从高位一次比较。

int compare(bign a,bign b)
{
    if(a.len>b.len)
        return 1;
    else if(a.len<b.len)
        return -1;
    else
    {
        for(int i=a.len-1;i>=0;i--)
        {
            if(a.d[i]>b.d[i]) return 1;
            else if(a.d[i]<b.d[i]) return -1;
        }
        return 0;    //两数相等
    }
}

大整数的四则运算

(1)高精度加法(2)高精度减法 (3)高精度与低精度的乘法(4)高精度与低精度的除法

一般不会考虑高精度与高精度的乘除法。

高精度加法

回忆小学时期的加法

从低位开始。

7+5=12   保留个位数2,向高位进1

4+6=10,再加上进位的1为11,保留个位数1,向高位进1

1+0=1,再加上进位的1,为2。

因此每一位进行加法的步骤:将该位上2个数字和进位相加,取个位数作为该位结果,取十位数作为新的进位。

bign add(bign a,bign b)   //高精度A+B
{
    bign c;
    int carry=0    //进位,初始位0;
    for(int i=0;i<a.len||i<b.len;i++)    //以较长的为界限
    {
        int temp=carry+a.d[i]+b.d[i];    //对应位与进位相加
        c.d[c.len++]=temp%10;
        carry=temp/10;
    }
    
    if(carry!=0)     //进位不为0,直接赋给结果的最高位。
    {
        c.d[c.len++]=carry;
    }
    return c;
}
#include <iostream>
#include <cstdio>
#include <cstring>

struct bign
{
    int d[1000];
    int len;
    bign()
    {
        memset(d,0,sizeof(d));
        len=0;
    }
};

bign change(char str[])
{
    bign a;
    a.len=strlen(str);
    for(int i=0;i<a.len;i++)
    {
        a.d[i]=str[a.len-i-1]-'0';
        // a.d[i]=str[a.len-i-1]-48;
    }
    return a;
}

bign add(bign a,bign b)   //高精度A+B
{
    bign c;
    int carry=0;    //进位,初始位0;
    for(int i=0;i<a.len||i<b.len;i++)    //以较长的为界限
    {
        int temp=carry+a.d[i]+b.d[i];    //对应位与进位相加
        c.d[c.len++]=temp%10;
        carry=temp/10;
    }

    if(carry!=0)     //进位不为0,直接赋给结果的最高位。
    {
        c.d[c.len++]=carry;
    }
    return c;
}

void print(bign a)
{
    for(int i=a.len-1;i>=0;i--)
    {
        printf("%d",a.d[i]);
    }
}

int main()
{
   char s1[100],s2[100];
   scanf("%s%s",s1,s2);
   bign a=change(s1);
   bign b=change(s2);
   print(add(a,b));
   return 0;
}

这样的写法是2个非负整数  如果一方是负的,可以再转换到数组时去掉负号,采用高精度减法,如果都是负的,采用高精度加法,最后结果取负。

高精度减法

回忆小学时期的减法

依旧从低位开始

5-7 失败,从高位借1,4变为3,10+5-7=8

3-6失败,从高位借1,1变为0,10+3-6=7

0-0=0    (要特别注意:减法后高位可能存在0,要忽视,但又要保证至少有一位数)

bign sub(bign a,bign b)
{
    bign c;
    for(int i=0;i<a.len||i<b.len;i++)
    {
        if(a.d[i]<b.d[i])
        {
            a.d[i+1]--;
            a.d[i]+=10;
        }
        c.d[c.len++]=a.d[i]-b.d[i];
    }
    while(c.len-1>=1&&c.d[c.len-1]==0)    //去除高位0,同时保证至少一位最低位
    {
        c.len--;
    }
    return c;

}

高精度乘法

高精度乘法这里指bign类型与int类型的乘法,其与之前的乘法运算有点不同。以147*35为例,这里把147视为bign类型数据,35视为int类型。在求积中始终把35看作整体。

7*35=245   取个位数5作为该位结果,高位24作为进位

4*35=140,加上进位24,164  把个位数4作为该位结果,高位16作为进位。

1*35=35,加上进位16,得51,把个位数1作为该位结果,高位5作为进位。

没有数继续乘了,就把进位5直接作为结果的高位。(注意在没数可乘时,如果进位大于10,需要循环进位)

bign multi(bign a,int b)
{
    bign c;
    int carry=0;    //进位
    for(int i=0;i<a.len;i++)
    {
        int temp=a.d[i]*b+carry;
        c.d[c.len++]=temp%10;
        carry=temp/10;
    }
    while(carry!=0)
    {
        c.d[c.len++]=carry%10;
        carry/=10;
    }
    return c;
}

高精度与高精度乘法

首先说一下乘法计算的算法,从低位向高位乘,在竖式计算中,我们是将乘数第一位与被乘数的每一位相乘,记录结果,之后,用第二位相乘,记录结果并且左移一位,以此类推,直到计算完最后一位,再将各项结果相加,得出最后结果。

计算的过程基本上和小学生列竖式做乘法相同。为了编程方便,并不急于处理进位,而是将进位问题留待最后统一处理。

    总结一个规律: 即一个数的第i 位和另一个数的第j 位相乘所得的数,一定是要累加到结果的第i+j 位上。这里i, j 都是从右往左,从0 开始数。
    ans[i+j] = a[i]*b[j];

    另外注意进位时要处理,当前的值加上进位的值再看本位数字是否又有进位;前导清零。

#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
using namespace std;

int size_plus;

void multi(string &s1,string &s2,int c[])
{
    int n=s1.size();
    int m=s2.size();
    int a[n];
    int b[m];
    int i,j;
    
    memset(a,0,sizeof(a));
    memset(b,0,sizeof(b));
    
    for(j=0,i=n-1;i>=0;i--)
    {
        a[j++]=s1[i]-'0';
    }
    
    for(j=0,i=m-1;i>=0;i--)
    {
        b[j++]=s2[i]-'0';
    }
    
    for(i=0;i<m;i++)
    {
        for(j=0;j<n;j++)
        {
            c[i+j]+=a[j]*b[i];
        }
    }
    
    for(i=0;i<size_plus-1;i++)
    {
        c[i+1]+=c[i]/10;
        c[i]=c[i]%10;
    }
}

int main()
{
    string s1,s2;
    cin>>s1>>s2;
    int size1=s1.size();
    int size2=s2.size();
    size_plus=size1+size2+5;
    
    int res[size_plus];
    memset(res,0,sizeof(res));
    multi(s1,s2,res);
    
    int i;
    for(i=size_plus-1;i>=0;i--)
    {
        if(res[i]!=0)
            break;
    }
    int len=i+1;
    for(int j=len-1;j>=0;j--)
    {
        printf("%d",res[j]);
    }
    return 0;
}

 

高精度与低精度的除法

除法运算与小学时的运算大致相同

1与7比较,不够除,商为0,余数为1

余数1和新位2组成12,12与7比较,够除,商为1,余数为5

余数5与新位3组合为53,53与7比较,够除,商为7,余数为4

余数4与新位4组合为44,44与7比较,够除,商为6,余数为2;

可以看出:上一步的余数乘以10加上该步的位,得到该步的临时被除数,将其与除数比较。若不够除,则商为0.要注意减法后高位可能会有多余的0,要忽视他们,但也要保证至少有一位数。

bign division(bign a,int b,int &r)   //有的题目野火要求球的余数。
{
    bign c;
    c.len=a.len;
    for(int i=a.len-1;i>=0;i--)   //从高位开始
    {
        r=r*10+a.d[i];
        if(r<b)
            c.d[i]=0;
        else
        {
            c.d[i]=r/b;
            r=r%b;  //新的余数
        }
    }

    while(c.len-1>=1&&c.d[c.len-1]==0)
    {
        c.len--;
    }
    return c;
}

整体测试

#include <iostream>
#include <cstdio>
#include <cstring>

struct bign
{
    int d[1000];
    int len;
    bign()
    {
        memset(d,0,sizeof(d));
        len=0;
    }
};

bign change_to_bign(char str[])
{
    bign a;
    a.len=strlen(str);
    for(int i=0;i<a.len;i++)
    {
        a.d[i]=str[a.len-i-1]-'0';
        // a.d[i]=str[a.len-i-1]-48;
    }
    return a;
}

int change_to_int(char str[])
{
    int ans=0;
    int len=strlen(str);
    int temp;
    for(int i=0;i<len;i++)
    {
        temp=str[i]-'0';
        // a.d[i]=str[a.len-i-1]-48;
        ans=ans*10+temp;
    }
    return ans;
}

bign add(bign a,bign b)   //高精度A+B
{
    bign c;
    int carry=0;    //进位,初始位0;
    for(int i=0;i<a.len||i<b.len;i++)    //以较长的为界限
    {
        int temp=carry+a.d[i]+b.d[i];    //对应位与进位相加
        c.d[c.len++]=temp%10;
        carry=temp/10;
    }

    if(carry!=0)     //进位不为0,直接赋给结果的最高位。
    {
        c.d[c.len++]=carry;
    }
    return c;
}

bign sub(bign a,bign b)
{
    bign c;
    for(int i=0;i<a.len||i<b.len;i++)
    {
        if(a.d[i]<b.d[i])
        {
            a.d[i+1]--;
            a.d[i]+=10;
        }
        c.d[c.len++]=a.d[i]-b.d[i];
    }
    while(c.len-1>=1&&c.d[c.len-1]==0)
    {
        c.len--;
    }
    return c;

}

bign multi(bign a,int b)
{
    bign c;
    int carry=0;    //进位
    for(int i=0;i<a.len;i++)
    {
        int temp=a.d[i]*b+carry;
        c.d[c.len++]=temp%10;
        carry=temp/10;
    }
    while(carry!=0)
    {
        c.d[c.len++]=carry%10;
        carry/=10;
    }
    return c;
}

bign division(bign a,int b,int &r)   //有的题目野火要求球的余数。
{
    bign c;
    c.len=a.len;
    for(int i=a.len-1;i>=0;i--)   //从高位开始
    {
        r=r*10+a.d[i];
        if(r<b)
            c.d[i]=0;
        else
        {
            c.d[i]=r/b;
            r=r%b;  //新的余数
        }
    }

    while(c.len-1>=1&&c.d[c.len-1]==0)
    {
        c.len--;
    }
    return c;
}

void print(bign a)
{
    for(int i=a.len-1;i>=0;i--)
    {
        printf("%d",a.d[i]);
    }
}

int main()
{
   char s1[100],s2[100];

   //模拟加法
   printf("请输入要进行加法操作的2个数\n");
   scanf("%s%s",s1,s2);
   bign a=change_to_bign(s1);
   bign b=change_to_bign(s2);
   printf("%s+%s=",s1,s2);
   print(add(a,b));
   printf("\n");

   //模拟减法
   printf("请输入要进行减法操作的2个数\n");
   scanf("%s%s",s1,s2);
   a=change_to_bign(s1);
   b=change_to_bign(s2);
   printf("%s-%s=",s1,s2);
   print(sub(a,b));
   printf("\n");

   //模拟乘法
   printf("请输入要进行乘法操作的2个数\n");
   scanf("%s%s",s1,s2);
   bign f=change_to_bign(s1);
   int d=change_to_int(s2);
   printf("%s*%s=",s1,s2);
   print(multi(f,d));
   printf("\n");

   //模拟除法
   printf("请输入要进行除法操作的2个数\n");
   int r=0;
   scanf("%s%s",s1,s2);
   a=change_to_bign(s1);
   int e=change_to_int(s2);
   printf("%s/%s=",s1,s2);
   print(division(a,e,r));
   printf(" %s%%%s=%d",s1,s2,r);
   printf("\n");
}

题目一   幂运算

给定两个数R和n,输出R的n次方,其中0.0<R<99.999, 0<n<=25

#include <cstdio>
#include <cstring>

struct bign
{
    int d[127];
    int len;
    bign()
    {
        memset(d,0,sizeof(d));
        len=0;
    }
};

bign change_str_to_bign(char str[],int *length)
{
    bign c;
    int len_=strlen(str);
    for(int i=len_-1;i>=0;i--)
    {
        if(str[i]=='.')
        {
            *length=len_-1-i;
        }
        else
            c.d[c.len++]=str[i]-'0';
    }
    return c;
}

int change_str_to_int(char str[])
{
    int c=0;
    int len_=strlen(str);
    for(int i=0;i<len_;i++)
    {
        if(str[i]!='.')
        {
            c=c*10+str[i]-'0';
        }
    }
    return c;
}

bign multi(bign a,int b)
{
    bign result;
    int carry=0;
    for(int i=0;i<a.len;i++)
    {
        int temp=a.d[i]*b+carry;
        result.d[result.len++]=temp%10;
        carry=temp/10;
    }
    while(carry)
    {
        result.d[result.len++]=carry%10;
        carry/=10;
    }
    return result;
}

int main()
{
    char str[7];
    int r;
    while(scanf("%s%d",str,&r))
    {
       int length=0;
       bign result;
       result.d[0]=1;
       result.len=1;

       bign a=change_str_to_bign(str,&length);
       int b=change_str_to_int(str);

       int number_of_minus=length*r;
       for(int i=0;i<r;i++)
       {
           result=multi(result,b);
       }

       //如果总位数小于小数位数,说明是纯小数
       if(result.len<=number_of_minus)
       {
           printf("0.");
           for(int i=0;i<number_of_minus-result.len;i++)
                printf("0");
           for(int i=result.len-1;i>=0;i--)
           {
               printf("%d",result.d[i]);
           }
       }

       //不是纯小数
        else
        {
            int j=0;
            while(result.d[j]==0&&j<=number_of_minus-1)
            {
                j++;
            }
            for(int i=result.len-1;i>=j;i--)
            {
                if(i+1==number_of_minus)
                    printf(".");
                printf("%d",result.d[i]);
            }
        }

    }
    return 0;

}

但是放到题目里边运行,总是提示我运行时间超时!可能是结构体很占用时间?

用字符串或数组表示大数,否则无法精确表示答案1的情况。同时注意2数相乘的位数。

函数BigDataMulti用于计算当前计算值(从数组尾到数组头为数字)与底数的乘积

#include <cstdio>
#include <cstring>

int len;

void BigDataMulti(int a[],int data)
{
    int i;
    int carry=0;
    for(i=0;i<len;i++)
    {
        int temp=a[i]*data+carry;
        a[i]=temp%10;
        carry=temp/10;
    }
    while(carry)
    {
        a[i++]=carry%10;
        carry/=10;
    }
    len=i;
}

int main()
{
    int n;   //指数
    char s[7];    //底数,最多为6位。
    int result[126]={0};    //存放结果,做多位5*25+1位;

    while(scanf("%s %d",s,&n)!=EOF)
    {
        int num=0;   //字符数组转化位整数的值
        int pos=0;
        int position=0;  //结果中小数位数
        for(int i=0;i<strlen(s);i++)
        {
            if(s[i]=='.')
                pos=strlen(s)-i-1;
            else
                num=num*10+s[i]-'0';
        }
        position=pos*n;

        result[0]=1;   //初始化结果为1,其中的结果是倒叙的,比如数组内容是12345时,其实结果为54321;
        len=1;   //结果长度初始化为1
        for(int i=0;i<n;i++)
        {
            BigDataMulti(result,num);
        }

        //输出
        if(len<=position)   //全为小数
        {
            printf("0.");
            for(int i=0;i<position-len;i++)
            {
                printf("0");
            }
            for(int i=len-1;i>=0;i--)
            {
                printf("%d",result[i]);
            }
        }

        else
        {
            int j=0;
            while(result[j]==0&&j<=position-1)   //去掉尾部的0,但也要防止非小数部分的0也被去掉。很重要!
            {
                j++;
            }
            for(int i=len-1;i>=j;i--)
            {
                if(i+1==position)
                    printf(".");
                printf("%d",result[i]);
            }


        }


    }
    return 0;
}

高精度加减乘除推荐博客

 

密码学分为两密码:对称密码和非对称密码。对称密码主要用于数据的加/解密,而非对称密码则主要用于认证、数字签名等场合。非对称密码在加密和解密时,是把加密的数据当作一个大的正整数来处理,这样就涉及到大整数的加、减、乘、除和指数运算等,同时,还需要对大整数进行输出。请采用相应的数据结构实现大整数的加、减、乘、除和指数运算,以及大整数的输入和输出。 【基本要求】 1.要求采用链表来实现大整数的存储和运算,不允许使用标准模板的链表(list)和函数。 同时要求可以从键盘输入大整数,也可以文件输入大整数大整数可以输出至显示器,也可以输出至文件。大整数的存储、运算和显示,可以同时支持二进制和十进制,但至少要支持十进制。大整数输出显示时,必须能清楚地表达出整数的位数。测试时,各种情况都需要测试,并附上测试截图;要求测试例子要比较详尽,各种极限情况也要考虑到,测试的输出信息要详细易懂,表明各个功能的执行正确。 2.要求大整数的长度可以不受限制,即大整数的十进制位数不受限制,可以为十几位的整数,也可以为500多位的整数,甚至更长;大整数运算和显示时,只需要考虑正的大整数。如果可能的话,请以秒为单位显示每次大整数运算的时间。 3.要求采用的设计思路,不允许出现以外的函数定义,但允许友元函数。主函数中只能出现的成员函数的调用,不允许出现对其它函数的调用。 4.要求采用多文件方式:.h文件存储的声明,.cpp文件存储的实现,主函数main存储在另外一个单独的cpp文件中。如果采用模板,则的声明和实现都放在.h文件中。 5.不强制要求采用模板,也不要求采用可视化窗口;要求源程序中有相应注释。 6.要求采用Visual C++ 6.0及以上版本进行调试。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值