【C语言】大数加减学习笔记

刷蓝桥杯题目的时候,看到了朋友写的A+B plus的题解,研究一番之后发现代码思路很简洁,适合我这种小菜鸡消化,于是自己重写了一遍,顺便推了个A-B plus,码了个总结。

在C/C++语言中,如果要计算两个整数A和B的和,一般情况下我们都直接让SUM = A + B,但如果A、B、SUM任意一个数超过了最长类型的规模,那程序将会出错。如何解决这样的问题呢?我们都知道,字符串可以根据用户需求存放非常多的字符,所以只需要让数字用字符串的形式存储下来就可以了。但是字符串可不能直接做加减运算,这时候我们就需要写一个模拟列竖式运算的程序。

  • (一)原料

我们需要两个字符串数组,用于从用户接收两个加数:

char a[10000] , b[10000];  //加数a、b

所有字符都需要转换成整型数字,才能进行运算:

int ac[10000] , bc[10000] , sum[10000];  //实际参与运算的三个数组

数组开的很大,而程序只需要对有效的数字进行运算,故需要知道有效数字长度:

int al=strlen(a) , bl=strlen(b) , suml;  //两个加数、得数的长度
  • (二)主函数

只需要输入两个加数,再执行求和函数就可以了:

int main()
{
     scanf(“%s%s”,a,b);
     add();
     return 0;
}
  • (三)求和函数——运算

让我们回忆一下小学学过的列竖式求和:

1  2  3

0  5  8

___1___

1  8  1

我们很容易想到如何让程序模拟上述的过程:假如输入a=123,b=58,那首先要让它们的每一位都对齐,也就是将58变成058,这样ac[0]、ac[1]、ac[2]上数字的数位就能分别和bc[0]、bc[1]、bc[2] 上数字的数位对应了。实现方法如下:

先把三个数组的每个元素都初始化为0:(知识点:内存操作函数)

memset(ac,0,sizeof(ac));
memset(bc,0,sizeof(bc));
memset(sum,0,sizeof(sum));

在接收了加数的字符串数组a、b中,从个位开始(逆向)取字符,将其转化为整型数字之后,正向地存入整型数组ac、bc中:(知识点:ASCII码转换)

int i;
for(i=al-1 ; i>=0 ; i--)
     ac[al-1-i] = a[i] - ‘0’;
for(i=bl-1 ; i>=0 ; i--)
     bc[bl-1-i] = b[i] - ‘0’;

这里的“先逆向取数,再正向存数”仅仅是为了方便写代码而已,因为我们总是习惯从数组的第一位开始向后遍历。

这么一来,我们就成功地将输入的加数列上了“竖式”,也就是说,它们现在已经做好了进行加法运算的准备(如图):

求出两个加数中最大的长度,暂时作为得数sum的长度suml,它不是sum数组的实际长度,它只是为了服务接下来遍历求和与答案输出的一个变量而已:

if(al>bl)
    suml=al;
else
    suml=bl;

下面就是核心部分——同时遍历两个加数数组进行求和。这部分的重点在于:ac[i]和bc[i]相加后,sum[i]每满10就要向前一位sum[i+1]进1,最后让原位sum[i]保留sum[i]对10取模的结果(也就是保留sum[i]个位数上的数):

for(i=0 ; i<suml ; i++)
{
     sum[i] += ac[i] + bc[i];
     sum[i+1] += sum[i] / 10;
     sum[i] = sum[i] % 10;
}
  • (四)求和函数——输出

经过了步骤(三)之后,数组sum[i]里存储的已经是最终答案,但我们在最开始时使用了“先逆向取数,再正向存数”的方法,所以在输出答案的时候,还需要多做一些处理:

不知道你还记不记得上文强调过“得数长度”——suml的值是暂时的,没错,两个数相加之后,得数可能比这两个数的任何一个都长,这是再常见不过的事情,我们还需要检查一下suml是否需要修正,以保证能够正确输出答案。但这其中有一个规律——对于n位数+n位数,得数的位数只可能是n或n+1,不可能是更高的位数,所以我们在程序中只需要判断suml+1位上是否有数即可:
 

if(sum[suml]!=0)   //如果得数确实增加了一位
suml++;    //那就让得数长度加1

最后将sum里的得数逆向输出(负负得正):

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

    printf(“%d”,sum[i]);

总代码如下:

#include<stdio.h>
#include<string.h>
char a[10000],b[10000];
int ac[10000],bc[10000],sum[10000];
int suml,i;
void add(){
    int al=strlen(a) , bl=strlen(b);
    if(al>bl)
       suml=al;
    else
       suml=bl;  

    memset(ac,0,sizeof(ac));
    memset(bc,0,sizeof(bc));
    memset(sum,0,sizeof(sum));

    for(i=al-1 ; i>=0 ; i--)
        ac[al-1-i] = a[i] - ‘0’;
    for(i=bl-1 ; i>=0 ; i--)
        bc[bl-1-i] = b[i] - ‘0’;

    for(i=0 ; i<suml ; i++){
        sum[i] += ac[i] + bc[i];
        sum[i+1] += sum[i] / 10;
        sum[i] = sum[i] % 10;
    }

    if(sum[suml]!=0)
        suml++;

    for(i=suml-1 ; i>=0 ; i--)
        printf(“%d”,sum[i]);
}


int main()
{
    scanf(“%s%s”,a,b);
    add();
    return 0;
}
  • (五)扩展——大数减法

减法区别于加法的地方在于:1、减法运算可能得到负数;2、加法逢十向前一位进一,而减法不够减向前一位借一。

为了让程序能正确输出负数,我们可以先判断被减数和减数哪个大,让程序固定用大数减小数,如果原式子得数是负数,则输出得数时在最前面增加负号即可:

int PorM=1;		//PorM是该数的正负状态,正为1,负为0
for(i=suml-1;i>=0;i--)	//从最高位开始遍历数组ac和bc
{
		if(bc[i]>ac[i])
 //假如在最高位i有bc[i]>ac[i],那么数bc必大于数ac,即减数>被减数
		PorM=0;		//减数>被减数,那么得数肯定是负数了
		break;
} 

if(!PorM) {	
//如果判断出得数是负数,就把减数与被减数调换,让程序固定用大数减小数
		int temp;
		PorM=0;
		for(i=0;i<len;i++)
		{
			temp = ac[i];
			ac[i] = bc[i];
			bc[i] = temp;
		}
}

在最后输出得数的时候,视PorM来决定是否输出负号就可以了。

接下来解决向前一位借1的问题,减法函数核心代码如下:

for( i=0 ; i<len ; i++)
{
sum[i] += ac[i] - bc[i]; 
ac[i+1] += sum[i]>=0 ? 0 : -1;    //向下一位借1
sum[i] = sum[i]<0 ? 10+sum[i] : sum[i];  	//原位运算后的数字
}

总代码如下:

#include <stdio.h>
#include <string.h>
char a[10000],b[10000]; 
int ac[10000],bc[10000],sum[10000];
int suml,i; 

void add(void) //减法函数
{
    int al = strlen(a) , bl = strlen(b);
    if(al > bl)   
        suml = al;
    else
        suml = bl;

    memset(ac,0,sizeof(ac));
    memset(bc,0,sizeof(bc));
    memset(sum,0,sizeof(sum));
    
    for( i=al-1 ; i>=0 ; i--)
        ac[al-1-i] = a[i] - '0';
    for(i=bl-1 ; i>=0 ; i--)
        bc[bl-1-i] = b[i] - '0';

	   int PorM=1;
	   for(i= suml -1;i>=0;i--){
		   if(bc[i]>ac[i])
		   PorM=0;
		   break;
	   } 

	   if(!PorM){
		   int temp;
		   PorM=0;
		   for(i=0;i< suml;i++){
			   temp = ac[i];
			   ac[i] = bc[i];
			   bc[i] = temp;
		   }
	   }

    //执行减法
    for( i=0 ; i< suml; i++){
        sum[i] += ac[i] - bc[i]; 
        ac[i+1] += sum[i]>=0 ? 0 : -1; 
        sum[i] = sum[i]<0 ? 10+sum[i] : sum[i];  
    }

    while(sum[suml -1]==0&& suml >1)//去除前端多余的0 (减法运算特有)
        suml--;
        
    if(PorM)
    {
        for( i= suml -1 ; i>=0 ; i--)
        	printf("%d",sum[i]);	
		}
		else
		{
			printf("-");
			for( i= suml -1 ; i>=0 ; i--)
        		printf("%d",sum[i]);
		}
}

int main()
{
    scanf("%s%s",a,b);
    add();
    return 0;
}

这种东西用C写起来也真是麻烦呀。以上如有疏漏或错误,敬请指出。

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

SeeChell

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

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

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

打赏作者

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

抵扣说明:

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

余额充值