也就是被同学问到怎么计算,才发现自己对这个知识点疏忽了,看来题做得还是不够全面。
问题提出
C++如何计算两个很大很大的数相加,这两个数大到连 long long 也装不下,请问如何计算他们之和?
本文不仅仅分析加法,同时一并把四则运算(加、减、乘、除)总结一下。
大数加法分析
在计算机中,数字是以二进制的形式存储的,因此对于数的存储以及计算方面具有很大的限制。比如:int 的范围是 -2147483648 ~ 2147483647,超过或者低于他的范围就会产生溢出,从而我们的计算将得到错误的结果。与之类似的缺点在浮点数的存储上也存在着。
那么一个大到连 long long 都无法存储的数应该如何计算呢?
我们的解决方法是字符数组。
让我们先来分析一下加法的步骤:
在小学我们就学过两个数的加法是如何计算的,如图:
加法完成的步骤:从低位开始计算,满十进一。
因此,我们不妨直接采用数组来存储每一位(个位、十位...)的字符,然后使用进位加法来计算结果。
那么具体怎么实现呢,我们按照上面的例子。
第一步,存储
这一步骤是完成输入与字符数组之间的转换。目的是方便我们后续的计算(通常我们都习惯从index为0处开始计算)。
具体来说做法不唯一,达到这个目的即可,我的两种思路如下:
- 正序输入,正序存储,然后调用 reverse 将字符数组逆序。
- 正序输入,直接采用反序存储(前提是知道多少位)。
第二步,创建ans数组
输入已经无法存储了,结果更不可能存储。我们需要建立一个以最大整数的位数 +1 为长度的字符数组,帮助我们存储进位。
第三步,遍历数组,逐位相加
从左到右遍历数组,逐位完成相加,同时要记录进位操作。
在我们的例子中,完成 index = 0 的加法,其中,将 5 存储在 index 对应位置,将 1 存储在 index + 1 的位置。
下一步,同理。计算 9 + 5 + 1(进位)。
最后一步,同理。计算3 + 8 + 1(进位)
第四步,逆序输出
同理,这里仍旧可以使用两种方法,当然直接逆序输出是最快的。也可以采用 reverse() 将结果转换成正序。
代码
//大数加法
#include<bits/stdc++.h>
using namespace std;
char a[200] = {'\0'};
char b[200] = {'\0'};
int res[205] = {0};
int carry[201]={0};
//get sum
void get_sum(char a[], char b[]){
int index = 0; //index
int tmp;
int len_a = strlen(a);
int len_b = strlen(b);
int len_max = len_a * (len_a >= len_b) + len_b * (len_a < len_b);
int x11[200] = {0};
for(int i = 0;i <= len_a - 1; i++)
{
x11[i]=a[i] - '0';
}
int x22[200] = {0};
for(int i = 0; i <= len_b - 1; i++)
{
x22[i]=b[i] - '0';
}
//不考虑进位对应位相加
for(int i = 0; i <= len_max - 1; i++)
{
res[i] = x11[i] + x22[i];
res[i + 1] = 0; //用于防止超过lenmax后的一位
}
//考虑进位 完成最终求和
for(int i = 0;i <= len_max + 1; i++)
{
carry[i+1] = res[i] / 10;
res[i] = res[i] % 10 + carry[i];
}
}
int main(){
int i;
//input
cin>> a >> b;
int len_a = strlen(a);
int len_b = strlen(b);
int len_max = len_a * (len_a >= len_b) + len_b * (len_a < len_b);
//reserve
strrev(a);
strrev(b);
//sum
get_sum(a, b);
if(res[len_max] == 0){
i = len_max - 1;
}else{
i = len_max;
}
for(; i >= 0; i--){
cout<<res[i];
}
return 0;
}
大数乘法分析
有了前面加法的分析与代码,我们对大数的乘法应该有了一定的思路吧?大数的乘法也跟我们小学的乘法一样,需要注意的是对应 index 的值存放的位置很关键,详细见如下分析过程。我们以 125 × 53 为例。
第一步 倒序输入
这里同样是倒序输入,不再展开。
第二步 逐位相乘
图中 Ans 的索引从左到右逐渐递增。
首先,我们计算 125 × 3。5 × 3 = 15,产生15个1;3 × 2 = 6,产生6个10;3 × 1 = 3,产生3个100。
下一步,我们计算 125 × 5。5 × 5 = 25,产生25个10;5 × 2 = 10,产生10个100;5 × 1 = 5,产生5个1000。
总结一个规律:即一个数的第 i 位和另一个数的第 j 位相乘所得的数,一定是要累加到结果的第 i+j 位上。这里i, j 都是从右往左,从0 开始数。即:res[i+j] = a[i] × b[j]。
第三步 进位加法
乘法步骤已经完成。我们需要做的就是将对应位置的进位加法处理完成即可。
第四步 逆序输出
最后进行逆序输出即可。这里也不展开了。
代码
//大数乘法
#include<bits/stdc++.h>
using namespace std;
char a[200] = {'\0'};
char b[200] = {'\0'};
int res[205] = {0};
//get_mul
void get_mul(char a[], char b[], int len_a, int len_b, int max_len){
int index = 0;
int x11[200] = {0};
for(index = 0; index < len_a; index++){
x11[index] = a[index] - '0';
}
int x22[200] = {0};
for(index = 0; index < len_b; index++){
x22[index] = b[index] - '0';
}
//最关键的一步
for(index = 0; index < len_a; index++){
for(int i = 0; i < len_b; i++){
res[i + index] += x11[index] * x22[i];
}
}
//处理进位
for(index = 0; index < len_a + len_b; index++){
if(res[index] > 9){
res[index + 1] += res[index] / 10;
res[index] %= 10;
}
}
}
int main(){
//input
cin >> a >> b;
int len_a = strlen(a);
int len_b = strlen(b);
int max_len = len_a * (len_a >= len_b) + len_b * (len_a < len_b);
int i = len_a + len_b;
//reserve
strrev(a);
strrev(b);
get_mul(a, b, len_a, len_b, max_len);
while(res[i] == 0){
i--;
}
for(; i >= 0; i--){
cout<<res[i];
}
return 0;
}