完整代码请扒拉到最底下↓。
解题要求:掌握基础的判断、循环、数组、字符串以及一些计算原理即可
“高精度”是怎么一回事?
简单来说,就是我们计算的数字太大太大了,导致已经超出了我们可以定义的范围,以至于我们不能用正常的定义来定义和计算数字,不然只能计算我们能所容纳的数字部分,无法达到要求。
比如longlong可以定义的范围在 -(2的63次方)到 2的63次方-1
如果此时数据过大,超过了longlong可以定义的范围,那么我们该如何计算它呢?这就需要我们运用到高精度的知识了。
题外话:Python没有这个限制,所以一行代码就解决了。
高精度计算的原理(以加法为例)
1.存储数据
你看既然我们数据太大,普通的定义都装不下了,那我们拿什么来装数据呢?没错,就是
数组
有同学可能会问:我们又不知道这个数有多长,我现在又不会用vector,(其实我现在也不会),那我们定义数组的时候应该定义多长的数组呢?
哎这可个是个好问题哇,题目给出的数据范围是:大于等于0,小于等于10的500次方,那10的500次方到底有多少位呢?
没想到这一步就难倒了我这个数学渣渣,但是我岂能因为这点困难而放弃,于是我选择了放弃参考大佬,但是我看了一圈下来都没人解释他们为什么要定义这么多位的数组或许是因为太简单了吧,所以我就在草稿纸上简单的算了一下:
10 ^ 2 = 100,有 3 位。
10 ^ 3 = 1000,有 4 位。
10 ^ 10 = 10000000000,有 11 位。
以此类推,10^500 表示 10 乘以 10 的 500 次方,即 10 * 10 * 10 * …(共 500 个 10 相乘)。因此,一个 “1” 后面跟着 500 个 “0”,所以以一共就是501位!
那么既然我们要计算两个数相加,就定义两个长度为501的数组,来容纳两个数:
int anum[501] = {0}, bnum[501] = {0};
不过别忘了我们的目的是计算两数之和,所以还要再定义一个数组,来容纳最终的结果:
int sum[502] = {0};
为什么是502位呢,同样举个例子:
假如只有两个最大的三位数相加,如999+999
999+999=1998;只有四位数
即总和的最大位数=原最大数字位数+1
接下来一步就是要输入数据了,但是这里有个坑,如果按照正常的顺序输入,算起来会非常麻烦,具体原因是什么呢?我们先看看我们是如何计算的
2.计算原理
其实计算原理我们早在小学就已经学过了,那就是
竖式运算
96
+ 69
-----------
165
假设上面的数是第一个数组的数,下边的数是第二个数组的数,那么我们就从个位开始熟悉的计算:
1.将每一位相加的结果存入第三个数组
2.如果相加结果大于10就需要存入结果个位的数
3.进位要保存一下,下一位相加的时候就要加上,如此反复。注意如果计算到最后一步也出现了进位,那么还要再多加一次循环,往多出来的一位存储进位
4.最终得到第三个数组排列的数值就是计算的结果
以下是代码:
//digit为相加后的进位
//remainder为余数
int digit = 0, remainder = 0;
//选择最长的那个数循环,并且考虑到最后一位可能进位,所以取“<=”
for (int i = 0; i <= max(al, bl); i++)
{
//当前位数=(两个数之和+上一位的余数)模10
//取模运算就是取余数
//当一个大于10的数模10时,可以取到这个数的个位数
//当一个小于10的数模10时,取到的就是这个数本身
remainder = (anum[i] + bnum[i] + digit) % 10;
//将求和取模的数存入对应的位置
sum[i] = remainder;
//计算进位,即当前两个数相加,再加上上一位的进位,再除以10
//当两个数相加再加上余数小于10时,除以10结果为0,即不进位
//当两个数相加再加上余数大于10时(肯定小于20,自己用最大的数举例可得),除以10结果为1,即需要进位
digit = (anum[i] + bnum[i] + digit) / 10;
}
3.输入数据
细心的同学已经发现了问题:
我们输入数据是从高位往低位输入的,换句话说,如果按照输入顺序存储,那我们输入的两个数据是“最高位对齐”的:
123
123456
但是我们竖式计算的要求是“个位对其”:
000123
123456
如果按照输入顺序存储数据,这样对我们计算有着很大的影响,由于每次输入的数据长度不一,想把下标对其非常困难,那我们怎么解决这个问题呢?
聪明的我大佬想到了一种方法,就是将输入的数据倒序存入数组,这样一来,两个数的第一位不就对其了吗?
321000
654321
细心的同学似乎又注意到一点:短一点的数字后边是用0补齐的,为什么不直接空着呢?
我们回顾一下上边关于计算部分的代码,就发现在整个循环中,始终都是要访问anum[i]和bnum[i]这两个元素的,如果其中一个数比另一个数短,那么短的那个数计算完之后,也仍然会访问它剩下的元素,所以我们不能让它空着,就拿0过来填位置。
接下来先讲怎么输入数据
由于int、longlong这些都装不下了,我们又不能直接往数组里边存,所以需要一个中介来暂存一下数据,接下来就轮到伟大的字符串登场了:
//由于int、longlong无法容纳足够多的数字,只能用string来存储:
string a, b;
cin >> a >> b;
然后咱们就开始把string里的字符倒序存入数组
//获取两个字符串的长度:
int al = a.length();
int bl = b.length();
//倒序将两个数字存入一个int数组里
//目的是将两个数从个位对其
for (int i = 0; i < al; i++)
{
//字符串a是可以变成a[i]的,每一个元素就是对应的字符
//-'0'可以将数字字符转换为数字
anum[i] = a[al - i - 1]-'0';//利用al - i - 1实现倒序
}
for (int i = 0; i < bl; i++)
{
bnum[i] = b[bl - i - 1]-'0';
}
4.最后差不多就剩下输出结果了
由于我们是倒序存储的,所以输出的时候就需要倒序输出,这样输出来就是正序了。
不过注意我们还要讨论一下最后一位是否有多出来的进位的情况,如果最后一位没有进位的话,那么最后一位的下一位就是0,我们就不能输出这个0,跳出这个循环继续下一个循环,用一个continue就能解决。
//因为我们是反向计算,所以最后需要反向输出,才能使数字为正序
//注意i从len开始而不是len-1,是因为当计算到最后一位时可能会有进位
//可能会比原最大位数还多出一位,所以要从最大位数+1的位置开始输出
for (int i = len; i >=0; i--)
{
//但是如果最大位数+1的地方并未进位,还是0的话,就需要跳过,这一位输出,不然整个数前边就会带着一个0
if (i == len && sum[len] == 0)
{
continue;
}
cout << sum[i];
}
以下是完整代码
#include <iostream>
using namespace std;
int anum[501] = {0}, bnum[501] = {0};
//这一步反而卡了挺久,因为不会vector,所以只能写一个定长数组,但是一直没想明白最大需要几位
//对于一个 10^500 的数值,它有 501 位。
//具体来说,10^500 表示 10 乘以 10 的 500 次方,即 10 * 10 * 10 * ...(共 500 个 10 相乘)。
//因此,它的字符串表示是一个 "1" 后面跟着 500 个 "0",共 501 位。
//举个例子:
//10 ^ 2 = 100,有 3 位。
//10 ^ 3 = 1000,有 4 位。
//10 ^ 10 = 10000000000,有 11 位。
//以此类推,10 ^ 500 就是 501 位。
//若两个数长度不一样,那么当短一点的数字计算完成之后,长一点的数字剩下部分需要继续计算
//所以短一点的数字后半部不能为空,而是为0,所以数组需要全部初始化为0
int sum[502] = {0};
//总和最多只有502位
//同样举个例子:
//假如只有两个最大的三位数相加,如999+999
//999+999=1998;只有四位数
//即总和的最大位数=原最大数字位数+1
int main() {
//由于int无法容纳足够多的数字,只能用string来存储:
string a, b;
cin >> a >> b;
//获取两个字符串的长度:
int al = a.length();
int bl = b.length();
//倒序将两个数字存入一个int数组里
//目的是将两个数从个位对其,才能计算
for (int i = 0; i < al; i++)
{
//字符串a是可以变成a[i]的,每一个元素就是对应的字符
//-'0'可以将数字字符转换为数字
anum[i] = a[al - i - 1]-'0';//利用al - i - 1实现倒序
}
for (int i = 0; i < bl; i++)
{
bnum[i] = b[bl - i - 1]-'0';
}
//digit为相加后的进位
//remainder为余数
int digit = 0, remainder = 0;
//找出两个数中最大的数字位数
int len = max(al, bl);
//从头开始遍历,直至最大的数字位数
for (int i = 0; i <= len; i++)
{
//当前位数=(两个数之和+上一位的余数)模10
//取模运算就是取余数,当一个数模10时,可以取到这个数的个位数,自己可以随便举几个数试试
remainder = (anum[i] + bnum[i] + digit) % 10;
//将求和取模的数存入对应的位置
sum[i] = remainder;
//计算进位,即当前两个数相加,再加上上一位的进位,再除以10
//当两个数相加再加上余数小于10时,除以10结果为0,即不进位
//当两个数相加再加上余数大于10时(肯定小于20,自己用最大的数举例可得),除以10结果为1,即需要进位
digit = (anum[i] + bnum[i] + digit) / 10;
}
//因为我们是反向计算,所以最后需要反向输出,才能使数字为正序
//注意i从len开始而不是len-1,是因为当计算到最后一位时可能会有进位
//可能会比原最大位数还多出一位,所以要从最大位数+1的位置开始输出
for (int i = len; i >=0; i--)
{
//但是如果最大位数+1的地方并未进位,还是0的话,就需要跳过,这一位输出,不然整个数前边就会带着一个0
if (i == len && sum[len] == 0)
{
continue;
}
cout << sum[i];
}
return 0;
}