大数运算/高精度

目录

大整数存储

大数运算

大数加法

计算原理---模仿竖式计算

实现

大数减法

计算原理---模仿竖式计算

实现

大数乘法

计算原理---模仿竖式计算

实现

大数除法

计算原理---将除法转化为减法进行运算

实现


大整数存储

       利用数组存储大整数,大整数的最低位存在数组的最前面,最高位存在数组的最后面,方便运算中的进位和添加位数.

大数运算

为什么使用 char 型数组存储大数?

       因为在运算过程中需要用到数据的位数,所以推荐使用字符串数组存储大数,通过利用 strlen 函数计算字符串长度得到数据的位数.

       使用 int 型数组存储大数时,需要自己计算数据的位数,不可以借助 strlen 函数进行计算.

为什么不能直接使用 ASSCII 码表示数据?

       因为使用 strlen 函数直接计算字符串长度时,从第一个字符开始计算字符串中字符数,直到遇到空字符,然后返回空字符前字符总个数.数据的某一位可能是 0,若使用 ASSCII 码直接进行存储, ASSCII 值为 0 的字符是空字符, strlen 函数遇到它,会直接返回字符长度,显然,此长度并非我们想要的.

        直接使用 ASSCII 码表示数据时,需要自己计算数据的位数,不可以借助 strlen 函数进行计算.

为什么需要进行前导零操作?

       在计算中获取计算到的最大位数,然后需要进行前导 0 操作,若不进行,可能会在最高位输出 0(0*78126).

字符转换成数字

       大数使用字符数组进行存储,不能直接进行运算,使用需要先转换成数字再进行运算.

str[i]=str[i]-'0';

大数加法

计算原理---模仿竖式计算

       从低位开始,一位一位模仿竖式进行计算,本位的计算结果可能会超过 10 但不会大于 99 ,所以要将十位记录在一个变量 k 中,在下一位计算开始时,要加上这个 k ,即进位.计算完后,要将本位结果存储在数组中,但是数组中只需存储个位,所以可以通过对 10 取模得到存储在数组中的数.依次类推,直到全部位数计算完毕.

怎么样判断计算完毕?

       两个字符串长度中最大长度跟计算执行次数息息相关,但是因为有可能进位,所以可能会多算一次,所以要通过判断 k 值是否为 0 去判断是否要再进行一次计算操作。

实现

1. 输入两个字符串,并计算其长度

2. 反转输入的两个字符串,使得低位位于数组下标小的一端,高位位于数组下标大的一端 [便于进位和添加位数] ,并将字符转换成数字

3. 进行大数加法

     a. 比较两个数的位数,取长度较长的作为循环的次数,即计算的次数

     b. 从低位依次计算c[i]=a[i]+b[i]+k,k 记录进位k=c[i]/10,数组只存储各位c[i]=c[i]%10

     c. 循环完毕后,判断进行最后一次计算之后 k 是否为0,是否需要进位,若k==0,则不需要进位;若k!=0,最高位需要进位,即最前位赋 1 ,结果位数+1

     d. 前导 0,从最高位开始判断,若其为 0,则结果位数 -1,即 len--,循环进行,直到找到第一位不是 0 的位置为止

4. 输出结果

#include<stdio.h>
#include<string.h>
#define N 100005
int lena=0,lenb=0,lenc=0;
char a[N]={0},b[N]={0},c[N]={0}; //初始化数组,a,b,c分别存储两个加数和结果
//大数加法 C=A+B
int bigAdd()
{
    int max=lena>lenb?lena:lenb,k=0,i=0;//比较a和b的长度,确定max,k记录进位,i记录循环的次数
    int len=0;//len记录结果c的位数
    while(i<max)
     {
         c[i]=a[i]+b[i]+k;//记得要加进位k
         k=c[i]/10;//取k
         c[i]=c[i]%10;//留个位 
         i++;
     }
    if(k!=0)//判断最后一次计算得到的进位k是否为0,若不为0,需向前一位进1,结果位数+1
          c[i]=k,c[i+1]='\0',i++;
    len=i;//len表示结果c的长度
    //前导0
    for(i=len-1;i>0 && !c[i];i--)//从最高位开始遍历,若为0,len--,直到找到第一个非0位为止
      {
          if(!c[i])
              len--;
      }
    return len;
}
//反转字符串
void reverse(char *a,int len)  //反转字符串,例如:123456789这个数,为了方便最后的补位,所以将数字的最低位放到数组的最前面
{
    for(int i=0;i<len;i++)//将字符转换成数字
        a[i]=a[i]-'0';
    for(int i=0;i<len/2;i++)//反转操作
    {
        int temp=a[i];
        a[i]=a[len-i-1];
        a[len-i-1]=temp;
    }
}
int main()
{
    scanf("%s%s",a,b);
    lena=strlen(a);//计算两个字符串的长度 
    lenb=strlen(b);
    reverse(a,lena);//反转字符串
    reverse(b,lenb);
    lenc=bigAdd();//进行大数运算
    for(int i=lenc-1;i>=0;i--)//输出结果
        printf("%d",c[i]);
    return 0;
}       

大数减法

计算原理---模仿竖式计算

       同样也是模仿竖式计算,从低位开始减,先要判断减数和被减数哪一位数长,被减数位数长是正常减,减数位数长,则减数-被减数,最后还要加上负号;两个位数长度相等时,最好比较哪一个数大,否则负号会处理的很繁琐.处理每一项时,要先按对应的位用被减数减去减数,用数组存入,如果差小于 0 ,需要向上一位借位,再把这一位的差加 10 ,依次类推.

实现

1. 比较被减数和减数的大小

       先比较被减数和减数的长度,若被减数 < 减数长度,则计算减数-被减数,并在计算结果之前添加"-";若被减数长度 ≥ 减数长度,则比较被减数和减数的大小,当被减数和减数相等时,直接输出结果 0 ,若被减数 > 减数,正常减即可,若被减数 < 减数,计算减数-被减数,在计算结果之前添加"-".

2. 进行大数减法运算

    a. 反转两个字符串,并将其转换成数字

    b. 按位相减并存入结果数组中

    c. 遍历结果数组,处理借位,将小于 0 的元素向高位借 1 ,使得本位 +10 ,高位  -1

3. 前导 0 ,从最高位开始判断,若其为 0,则结果位数 -1,即 kk--,循环进行,直到找到第一位不是 0 的位置为止

4. 输出结果

#include<iostream>
#include<cstring>
using namespace std;
#define M 100005
char s1[M], s2[M];//s1,s2分别存被减数和减数
int a[M] = { 0 }, b[M] = { 0 }, c[M] = { 0 };a,b分别存反转之后的数值型的被减数和减数
void ss(char s1[], char s2[], int n, int m)
{
	int i;
	for (i = 0;i < n;i++)//将字符转换为数字
		a[i] = s1[n - i - 1] - '0';
	for (i = 0;i < m;i++)//将字符转换为数字
		b[i] = s2[m - i - 1] - '0';
	for (i = 0;i < n;i++)
		c[i] = a[i] - b[i];//按位减后将结果存入数组c
	for (i = 0;i < n;i++)
	{
		if (c[i] < 0)//若数组c的某一位数<0,说明需要向前一位借1
		{
			while (c[i] < 0)
			{
				c[i + 1] = c[i + 1] - 1; //借位,高位减一,低位加十
				c[i] += 10;
			}
		}
	}
}
int main()
{
	int n, m, k, kk;//n,m存被减数和减数的长度,kk存结果的位数
	while (~scanf("%s%s", s1, s2))
	{
		n = strlen(s1);
		m = strlen(s2);
		printf("s1=%d s2=%d\n", n, m);
		//比较被减数和减数
		if (n > m)//被减数>减数
			ss(s1, s2, n, m);
		else if (n < m)//被减数<减数
		{
			printf("-");
			ss(s2, s1, m, n);
		}
		else//被减数与减数的位数相同
		{
			k = strcmp(s1, s2); //strcmp()函数:从第一位开始比较两个字符串的大小(不管位数多少)k>0,则s1较大;k<0,则s2较大;k=0,则s1=s2
			if (k == 0)//被减数==减数
			{
				printf("0\n");
				continue;
			}
			else if (k > 0)//被减数>减数
				ss(s1, s2, n, m);
			else//被减数<减数
			{
				printf("-");
				ss(s2, s1, m, n);
			}
		}
		if (n > m)
			kk = n;
		else
			kk = m;
		while (c[kk-1] == 0)
			kk--; //排除前导0
		for (int i = kk-1;i >= 0;i--)
			printf("%d", c[i]);
		printf("\n");
	}
	return 0;
}

大数乘法

计算原理---模仿竖式计算

       乘法运算,有时候用一个小数乘大数,有时候用一个大数乘另一个大数,小数乘大数可以只将大数放在数组中计算,较为简单,此处只讲解大数乘大数.

       进行竖式模拟,将两数中位数多的作为算法中的被乘数【竖式上面的那个】,用乘数的每一位乘被乘数.在每位乘的过程中是一个大数乘一个小数的算法,和大数加法类似.从低位算,一位一位计算,将结果里高十位的记录在一个变量 k 中,在下一位计算开始要加上这个 k.

实现

1. 反转两个字符串,并将字符转换成数字

2. 处理两个字符串,将位数多的数作为被乘数,位数少的数作为乘数,设置指针 l 和 r , l 指向位数多的数, r 指向位数少的数,设置两个变量 lena 和 lenb , lena 存储 l 指向的数的位数, lenb 存储 r 指向的数的位数

3. 大数相乘

     a. 用乘数的每一位分别乘以被乘数的每一位,乘数的位数限制外层循环的次数,被乘数的位数限制内层循环的次数,每一轮计算完成之后都需要更新结果的位数

     b. 每次按位相乘,结果的每一位是在该位原来的基础上加上按位相乘新得到数和进位

     c. 每次更新进位,即取十位

     d. 每次计算保留个位存储在结果的该位置上

     e. 前导 0 ,从最高位开始判断,若其为 0,则结果位数 -1,即 kk--,循环进行,直到找到第一位不是 0 的位置为止

4. 输出结果

#include<stdio.h>
#include<string.h>
#define N 100005
char a[N]={0},b[N]={0},c[N]={0};//a,b分别存两个乘数,c存结果
int bigMI(char*a,int lena,char*b,int lenb)
{
    int k=0,i=0,j=0,l=0;
    int len=0;
    while(i<lenb) //代表乘的次数,在竖式中乘数每个位都要乘被乘数
    {
        l=i;
        j=0;
        while(j<lena || k!=0)
              {
                  c[l]+=b[i]*a[j]+k; //按位相乘,别忘记加上进位k,c[l]是在原来的基础上加上新计算得到的数
                  k=c[l]/10;//更新进位,即取十位
                  c[l]%=10;//保留个位作为结果
                  j++;
                  l++;
                  if(len<l)//更新结果c的位数
                            len=l;
              }
        i++;
    }
    for(i=len-1;i>0 && !c[i];i--)//前导0
    {
        if(!c[i])
            len--;
    }
    return len;
}
void reverse(char*a,int len)//反转,转换
{
    for(int i=0;i<len;i++)//将字符型数转化为数值型数
        a[i]-='0';
    for(int i=0;i<len/2;i++)//反转字符串
    {
        int temp=a[i];
        a[i]=a[len-i-1];
        a[len-i-1]=temp;   
    }
}
int main()
{
    scanf("%s%s",a,b);
    int lena=0,lenb=0;//存两个数的长度,lena是长度长的数的位数,kenb是长度短的数的位数
    lena=strlen(a);//开始时,lena和lenb存两个数的长度
    lenb=strlen(b);
    reverse(a,lena);//反转两个字符串,并将字符转换为数字
    reverse(b,lenb);
    char* l,*r;//计算时,被乘数l选两个因数中长度较长数,乘数r是较短的数
    if(lena>lenb){//将长度较长的数赋给l,较短的赋给r
        l=a;
        r=b;
    }
    else
    {
        l=b;
        r=a;
        int temp=lena;//若lena<lenb,则交换两个数的长度,使得lena存长的,lenb存短的
        lena=lenb;
        lenb=temp;
    }
    int lenc=bigMI(l,lena,r,lenb);//大数相乘,返回结果的长度
    for(int i=lenc-1;i>=0;i--)//输出
        printf("%d",c[i]);
    return 0;
}

大数除法

计算原理---将除法转化为减法进行运算

  1. 不停的用被除数位首(从除数第一位 [高位] 开始与被除数相等的那几位)减去除数,直到被除数位首小于除数
  2. 将次数保存在结果数组中,然后在被除数的前面添一个 0 继续相减
  3. 按照以上方法继续运算,直到被除数位尾(从被除数最后一位开始与除数位数相等的那几位)相减之后即可结束

实现

1. 特殊情况判断,当被除数小于除数时,商为 0 ,余数为除数本身,输出商和余数

2. 一般情况,循环进行减运算 [类似于大数减法] ,直到不能减为止,记录执行减运算的次数,即商的每一位.进行一轮减运算之后,需要将除数的每一位右移一位,相应的增加除数位数,将除数的最低位的下一位置为空字符 [因为 strcmp 函数执行时遇到空字符结束,并返回比较的结果] ,开始新一轮运算,特殊地,在第一次右移之后,为除数的高位赋 0

3. 前导 0 ,从最高位开始判断,若其为 0,则结果位数 -1,即 kk--,循环进行,直到找到第一位不是 0 的位置为止

4. 输出商

5. 求余数,进行多轮减运算之后,被除数记录的是余数,但是其高位为 0 ,只有低位存余数.若余数为 0 ,则被除数的每一位存 0 ,最低位的下一位存空字符,作为退出循环的条件

6.特判余数为 0 的情况,并输出余数 0

7. 输出余数

#include<stdio.h>
#include<string.h>
#define M 1000005
char s1[M],s2[M];
int a[M];
void ss(int m) //减运算
{
    int i=0,j;
    while(1)//每次减完之后,高位会变为0,那么下次计算的时候就不需要再对这些位进行计算,找到第一位非零位开始进行计算
    {
        if(s1[i]=='0')
             i++;
        else
        {
            j=i;//j记录第一位非零位,即每次进行减运算的第一位,即高位
            break;
        }
    }
    for(;i<m;i++)//大数减法
         s1[i]=s1[i]-s2[i]+'0';//将字符结果转换为数字结果
    for(i=m-1;i>j;i--)//处理借位,向高位借位,高位-1,低位+10
         if(s1[i]<'0')
              {
                  s1[i]+=10;
                  s1[i-1]--;
              }
}
int main()
{
    int i,j,k,n,m;
    while(~scanf("%s%s",s1,s2))
    {
        n=strlen(s1);
        m=strlen(s2);
        if(n<m || n==m && strcmp(s1,s2)<0) //特殊情况判断,被除数小于除数
        {
            printf("0余数="); //求余数
            for(i=0;i<n;i++)
                 printf("%d",s1[i]-'0');
            printf("\n");
            continue;
        }
        k=0;//k为商的下标
        while(1)
        {
            a[k]=0;
            while(strcmp(s1,s2)>=0) //循环进行减运算,直到不能减为止
            {
                ss(m);
                a[k]++;//记录执行减运算的次数,即商的每一位
            }
            k++;
            if(n==m)
            break;
            for(i=m-1;i>=0;i--)//进行一轮减运算后,需要将除数的每一位右移一位,开始新一轮运算
                 s2[i+1]=s2[i];
            s2[0]='0';//特殊操作,在第一次右移之后,为除数的高位赋0
            m++;//除数位数增长
            s2[m]='\0'; //strcmp()函数遇到字符'\0'结束
        }
        i=0;
        while(a[i]==0) //去除前导0
            i++;
        for(;i<k;i++)//输出商
            printf("%d",a[i]);
        printf("余数=");
        j=0; //求余数
        while(s1[j]=='0')//进行多轮减运算之后,s1记录的是余数,但是s1的高位为0,只有低位存余数,所以需要遍历s1,找到第一位非零位,即余数的高位
            //若余数为0,则s1[0]~s[n-1]均为0,s1[n]为空字符,推出循环时j==n
             j++;
        if(j==n)//特判,余数为0的情况
        {
            printf("0\n");
            continue;
        }
        for(;j<n;j++)//输出余数
            printf("%d",s1[j]-'0');
        printf("\n");
    }
return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

何hyy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值