目录
大整数存储
利用数组存储大整数,大整数的最低位存在数组的最前面,最高位存在数组的最后面,方便运算中的进位和添加位数.
大数运算
为什么使用 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 ,结果位数+1d. 前导 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;
}
大数除法
计算原理---将除法转化为减法进行运算
- 不停的用被除数位首(从除数第一位 [高位] 开始与被除数相等的那几位)减去除数,直到被除数位首小于除数
- 将次数保存在结果数组中,然后在被除数的前面添一个 0 继续相减
- 按照以上方法继续运算,直到被除数位尾(从被除数最后一位开始与除数位数相等的那几位)相减之后即可结束
实现
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;
}