C语言中的大数运算(加减乘除、阶乘、2的N次方)
作者:互联网
目录
前言
之前是写过一些大数运算的题的,然后一个学期没看代码,一些细节又不会了,所以就打算拿一些大数运算的题做一个汇总,方便自己以后理解,起码证明自己学过。大数运算其实就是一维数组的应用,大数的加减乘除和大数阶乘都可以做出来的一维数组应该就算很熟练了(菜鸟不敢说大话)。另外说明一下,博主是用Openjudge网站上NOI里面的题目来写的,不过和其它网站的题应该是大同小异。这里附上网站的链接:http://noi.openjudge.cn/ 有兴趣的可以去刷一下,题目很适合刚学C语言的同学。还有就是,因为博主用的基本都是相同的变量,所以当你们弄懂了第一题大整数加法后,后面的就比较容易理解。
一、大整数加法
题目是这样的:
我们知道C语言中整型变量是有范围的,int的范围就是21亿,也只是10位数,更多的long int(长整型)也不过是十九位,而题目要求是不超过200位数,显然不可能直接用整型变量来相加。这时候就要用到数组了,我们只需要200左右大小的数组就可以完全解决题目的要求。但是显然,按题目要求我们只能输入两次,所以我们要用字符数组来进行输入。
{
char number1[205],number2[205];//为了防止加减进位时超过200位,数组大小要比题目要求大小大一些。
scanf("%s %s",&number1,&number2);
也可以这样输入:gets(number1);gets(number2);
}
输入完成后就要进行加法运算了,显然不能用字符数组直接进行运算,所以先把字符数组转为数字数组。在这之前我们需要知道字符数组的长度,这时候要用到strlen()函数,头文件是string.h。
#include<stdio.h>
#include<string.h>
int main()
{
char number1[205],number2[205];
int num1[205]={0},num2[205]={0};//后面的={0}不能省略,这样可以确保数字数组初始化的全部值为0
gets(number1);gets(numbers);
int n=strlen(number1),m=strlen(number2);//求字符数组的长度
}
接下来就要开始转换了,需要注意的是,在输入字符数组的时候,我们是按高位到低位的顺序输入的, 相加的时候要从最后面开始相加,所以在转换的时候要进行倒置。为什么要进行倒置呢?不倒置的时候,有一个问题不好解决:进位问题。不进行倒置,那我们开头下标0对应的数字就是最高位,当相加的两个数字位数相同并且相加要进位时,我们就得把所有数字往后挪一位,当然,这样的问题我们可以在转换的时候控制下标进行解决,但是,我们不知道相加是否进位了。。。总之,不进行倒置的话要控制下标博主认为是很麻烦的,进行倒置的话这些问题都没有,只需要解决一下前导0的问题。下面进行数组转换:
#include<stdio.h>
#incldue<string.h>
int main()
{
char number1[205],number2[205];
int num1[205]={0},num2[205]={0};
gets(number1);gets(number2);
int n=strlen(number1),m=strlen(number2);
for(int i=0;i<n;i++){
num1[i]=number1[n-i-1]-'0';
}
for(int i=0;i<m;i++){
num2[i]=number2[n-i-1]-'0';
}
}
具体解释一下倒置:
拿number1='123456789'位例子进行倒置。
显然,长度n=9
字符数组长度为9,数字数组长度应该也为9。
字符数组和数字数组的下标都是从0开始的,那么对应的下标是0~8
那我们num1的第一位(对应下标0)对应的下标应该是:8
第二位:下标为7
下面简单示意(前一个是数字数组下标,后一个是字符数组下标):
0~8 1~7 2~6 ... 8~0
在for(int i=0;i<n;i++) 中,显然就是 num1[1]对应number1[n-i-1]
然后下一步就是进行加法运算了:
加法运算就是从低位开始进行相加,若是低位的数字相加后大于等于10,那么就要进行进位,
就是前一位的数字加1,进位后原来的低位数字-10.
拿999+123来举例:
999
+ 123
---------
1122
具体的过程没法写出来,同学们可以自己拿笔在草稿纸上比划一下
加法运算:
#include<stdio.h>
#include<string.h>
int main()
{
char number1[205],number2[205];
int num1[205]={0},num2[205]={0};
gets(number1);gets(number2);
int n=strlen(number1),m=strlen(number2);
for(int i=0;i<n;i++){
num1[i]=number1[n-i-1]-'0';
}
for(int i=0;i<m;i++){
num2[i]=number2[m-i-1]-'0';
}
//取长度较大的一个进行循环:
int k;
if(n>m) k=n;
else k=m;
//下面进行相加:
for(int i=0;i<k;i++){
num1[i]+=num2[i];
//解决进位问题:
if(num1[i]>=10){
num1[i+1]++;//因为是个位数相加,所以没次进位最多是1
num1[i]-=10;
}
}
}
这样就加好了!最后就是前导0问题和输出问题了,因为我们进行了倒置,所以低位数在前面,前导0会在最后面,我们需要进行逆序输出。因为不知道在长度为k的数组里,最高位数前面有多少个0,所以用while循环:
int i=k;//直接用k也可以
while(num1[i]==0) i--;
if(i<0) printf("0");//防止输入的数是多个0相加
for(;i>=0;i--)
printf("%d",num1[i]);
return 0;
到这里,问题就解决了!下面是完整代码:
#include<stdio.h>
#include<string.h>
int main()
{
char number1[205],number2[205];
int num1[205]={0},num2[205]={0};
gets(number1);gets(number2);
int n=strlen(number1),m=strlen(number2);
for(int i=0;i<n;i++){
num1[i]=number1[n-i-1]-'0';
}
for(int i=0;i<m;i++){
num2[i]=number2[m-i-1]-'0';
}
int k;
if(n>m){k=n;}else{k=m;}
for(int i=0;i<k;i++){
num1[i]+=num2[i];
if(num1[i]>=10){
num1[i+1]++;
num1[i]-=10;
}
}
int i=k;
while(num1[i]==0) i--;
if(i<0) printf("0");
for(;i>=0;i--){
printf("%d",num1[i]);
}
return 0;
}
二、大整数减法
题目是这样的:
大整数的加法和减法是差不多的,就是加法变成了减法,将大数加法里的加法运算改一下就差不多了,博主就不多说了,直接上完整代码:
#include<stdio.h>
#include<string.h>
int main()
{
char number1[205],number2[205];
int num1[205]={0},num2[205]={0};
gets(number1);gets(number2);
int n=strlen(number1),m=strlen(number2);
for(int i=0;i<n;i++){
num1[i]=number1[n-i-1]-'0';
}
for(int i=0;i<m;i++){
num2[i]=number2[m-i-1]-'0';
}
int k;
if(n>m){
k=n;
}else{
k=m;
}
for(int i=0;i<k;i++){//主要在这一部分改动
num1[i]-=num2[i];//
if(num1[i]<0){//
num1[i+1]--;//
num1[i]+=10;//
}
}
int i=k;
while(num1[i]==0) i--;
if(i<0) printf("0");
for(;i>=0;i--){
printf("%d",num1[i]);
}
return 0;
}
三、大整数乘法
题目是这样的:
大整数的乘法就加法复杂了一些,大致的思路还是一样的。嗯~就是在进行乘法运算时比较麻烦,看了一个大佬的文章,都不知道怎么用自己的语言概括了。。。我直接上我自己写的代码吧:
#include<stdio.h>result[i+j]=num1[i]*num2[j],其本质是num1[i]个10^i与num2[j]各10^j相乘,这样得到的结果是num1[i]*num2[j]*10^(i+j),那么该结果正好是在第i+j位上,所以这样才会出现所谓的规律,其本质是计算的方式限定的
#include<string.h>
int main()
{
char number1[1000],number2[1000];
int num1[1000]={0},num2[1000]={0},result[1000]={0};
gets(number1);gets(number2);
int n=strlen(number1),m=strlen(number2);
for(int i=0;i<n;i++){
num1[i]=number1[n-i-1]-'0';
}
for(int i=0;i<m;i++){
num2[i]=number2[m-i-1]-'0';
}
int k=n+m;//由乘法规律知道,相乘结果的位数不会超过m+n
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
result[i+j]+=num1[i]*num2[j];//result[i+j]=num1[i]*num2[j],其本质是
}//num1[i]个10^i与num2[j]各10^j相乘,这样得到的结果是num1[i]*num2[j]*10^(i+j),
} //那么该结果正好是在第i+j位上,所以这样才会出现所谓的规律,其本质是计算的方式限定的
for(int i=0;i<k;i++){
if(result[i]>=10){
result[i+1]+=result[i]/10;
result[i]%=10;
}
}
int i=k;
while(result[i]==0) i--;
if(i<0) printf("0");
else for(;i>=0;i--){
printf("%d",result[i]);
}
return 0;
}
我再辅助个图:
后面以此类推。
四、大整数除法
大数除法是大数运算中最难的。
首先我们要明白什么是被除数和除数:10/5=2,看这个例子,10是被除数,5是除数,2是商,余数为0;不清楚可以百度看看(小学知识)。
大数除法的核心是:如果被除数小于除数,那么可以直接得出商为0,余数就是被除数;如果被除数等于除数,商为1,余数为0;如果被除数大于除数,那么看被除数是除数的几倍,也就是看被除数有几个除数,如13/10=1.3,商是1,余数是3,所以被除数13里面最多有1个10,剩下是余数。所以被除数大于除数时,我们可以用被除数不断减去除数,最后可以得出结果。
但是,一个一个的减太过暴力,所以我们要根据位数来进行减法。这里就用到大数减法的知识了。当被除数位数大于除数位数时,我们可以将除数补位,是除数与被除数位数相同,再进行相减,下面直接上代码,讲解在注释里:
#include<stdio.h>
#include<string.h>
char number1[100],number2[100];//字符串形式输入大数
int num1[100]={0},num2[100]={0},a[100]={0},b[100]={0};//被除数 除数 商 余数
int digit; //大数的位数
void sub(int num1[],int num2[],int n,int m){//大数减法
for(int i=0;i<n;i++){
if(num1[i]<num2[i]){
num1[i]=num1[i]+10-num2[i];
num1[i+1]--;
}
else{
num1[i]=num1[i]-num2[i];
}
}
for(int i=n-1;i>=0;i--){
if(num1[i]){
digit=i+1;
break;
}
}
}
int judge(int num1[],int num2[],int n,int m)//判断被除数与除数的大小
{
int i;
if(n<m)
return -1;
if(n==m){
for(i=n-1;i>=0;i--){//对应位的位数相等
if(num1[i]==num2[i])
continue;//因为是从高位数开始比较,所以高位数大的数就大
if(num1[i]>num2[i])//被除数 大于 除数,返回1
return 1;
if(num1[i]<num2[i])//被除数 小于 除数,返回-1
return -1;
}
return 0;//能运行到这说明被除数等于除数,返回0
}
}
int main()
{
int i,j=0,k=0,t;
gets(number1);gets(number2);
int len,n=strlen(number1),m=strlen(number2);
for(i=n-1,j=0;i>=0;i--){//一样的将字符数组倒置存到数字数组里
num1[j++]=number1[i]-'0';
}
for(i=m-1,k=0;i>=0;i--){
num2[k++]=number2[i]-'0';
}
if(n<m){//被除数的位数小于除数,说明被除数小于除数,可
printf("0\n");//以直接得出结果0和余数
printf("%s",number1);
}
else{
len=n-m;//两个大数位数的差值
for(i=n-1;i>=0;i--){//如果n>m,那么要在除数的后面补0
if(i>=len){//因为是倒置运算,所以是在数组前面补0
num2[i]=num2[i-len];
}else{
num2[i]=0;
}
}
m=n;//补0之后两个大数的位数是相等的
digit=n;
for(j=0;j<=len;j++){
a[len-j]=0;
while(((t=judge(num1,num2,n,m))>=0)&&digit>=k){//判断被除数与除数的大小和被除数与原位数的关系
sub(num1,num2,n,m);//大数减法函数
a[len-j]++;//储存商的每一位
n=digit;//重新修改被除数的长度
if(n<m&&num2[m-1]==0)
m=n;
}
if(t<0){//若被除数小于除数,除数减小一位
for(i=1;i<m;i++){
num2[i-1]=num2[i];
}
num2[i-1]=0;
if(n<m)
m--;
}
}
i=len;
while(a[i]==0) i--;
for(;i>=0;i--){//商
printf("%d",a[i]);
}
printf("\n");
for(i=n;i>0;i--){
if(num1[i]!=0)
break;
}
for(;i>=0;i--){//余数
printf("%d",num1[i]);
}
}
return 0;
}
五、大数阶乘
题目是这样的:
简单讲讲解决问题的核心思路:不断将一个数组中的每个数字都乘于循环次数。同时要不断更新最高位数。 博主试过用暴力循环的方法,但是提交后会报超时。特别要注意的是:0!(0的阶乘)=1,不是0!!!博主因为这个错了好多次。直接给出完整代码,在代码里注释解释:
#include<stdio.h>
int main()
{
int n,num[100000]={1};
scanf("%d",&n);
int m=1,j;
for(int i=2;i<=n;i++){
for(j=0;j<m;j++){
num[j]*=i;
}
for(j=0;j<m;j++){//要注意哦,我们依然是倒置计算,最后逆序输出
if(num[j]>=10){
num[j+1]+=num[j]/10;
num[j]%=10;
}
}
if(num[j]>0){//单独对最高位进行进位,因为我们知道,任何一个数对10求模后都为个位数
while(num[j]>=10){//所以除了最高位数,其他位数一定为个位数,但是,因为我们要进行
num[j+1]+=num[j]/10;//的是大数阶乘,最高位数的前一位除于10后的数字可能会超
num[j]%=10;//过2位数,加到最高位后会使最高位数超过一位数,所以要对最高位数
j++;//单独进位,同时求出最高位数的位置m
}
}
m=j+1;
}
int i=m;//这里用int i=99999;也是可以的,别问为什么,问就是博主试过
while(num[i]==0) i--;//一样的去前导0,就不重复说了
for(;i>=0;i--){
printf("%d",num[i]);
}
return 0;
}
六、计算2的N次方
题目是这样的:
计算2的n次方其实和大数乘法差不多,和大数阶乘也类似,而且其实更加简单,不需要使用字符数组就可以解决。简单说一下思路,用一个数字数组(范围足够大),将第一位赋值为1,其它为0。然后就开始累乘,也就是将数组中的每一位都乘于2,之后进位,在累乘的过程中,我们不知道某一时刻的位数,也就是数组中非0数字的个数。不过我们可以暴力点:直接用一个不可能超过的位数来进行循环。 还需要注意的一点是:要将相累乘于进位分开,不然结果是不对的。 拿一个3位数乘以2就知道了,每一位数乘于2之后在进行进位。这和大数乘法是一样的。下面是完整代码:
#include<stdio.h>
int main()
{
int n,num[100000]={1};//让数组第一位为1,其它为0
scanf("%d",&n);
for(int i=0;i<n;i++){
for(int j=0;j<100000;j++){//2的100次方的位数不可能超过10w
num[j]*=2;
}
for(int j=0;j<100000;j++){//因为是求2的n次方,所以每位数乘于2不会超过二位数
if(num[j]>=10){//所以可以不用单独对最高位数求进位,不过求也没关系
num[j+1]+=num[j]/10;
num[j]%=10;
}
}
}
int i=99999; //要对应所设数组的最大下标,也就是num[99999]
while(num[i]==0) i--;//这里去掉前导0
for(;i>=0;i--){
printf("%d",num[i]);
}
return 0;
}
当然,我们也可以不用这种暴力的解法,其实只要找出最高位对应的下标,拿这个下标进行循环就行了,和前面的大数阶乘是一样的,下面是代码:
#include<stdio.h>
int main()
{
int n,num[100000]={1};
scanf("%d",&n);
int m=1,j;
for(int i=0;i<n;i++){
for(j=0;j<m;j++){
num[j]*=2;
}
for(j=0;j<m;j++){
if(num[j]>=10){
num[j+1]+=num[j]/10;
num[j]%=10;
}
}
m=j+1;
}
int i=m;
while(num[i]==0) i--;
for(;i>=0;i--){
printf("%d",num[i]);
}
return 0;
}
标签:10,num1,int,C语言,num,number2,number1,阶乘,加减乘除 来源: https://blog.csdn.net/xiexieyuchen/article/details/118414679