事先声明:
需要注意的是,这里我们只讲述最基础的实现,对于以下算法的拓展与优化可自行进行相关操作。谢谢!
一、引言
前面,我们详细介绍了高精度加法的思路构建与代码实现,讲述了从0到完成的代码实现过程,如果看过的小伙伴,大概可以理解编写高精度算法的基本思路和框架,也就是利用数组的思想。如果没看过,还是建议先了解一下高精度加法的详细实现思想,点击这里可快速进入上一次的高精度加法讲述中,当然,你也可以去自己认为讲解更加清楚的其他文章中进行学习,我们旨在学懂,其他并不重要。
在高精度加法中尤其要注意的就是关于进位所产生的那些事,这是加法运算的特有逻辑,那么对于现在要讲述的高精度减法的算法实现,他也会有自己的特点,那就是借位的处理。基于前面加法的实现,我们完全可以触类旁通,举一反三,换句话说,实际上,高精度加法和减法包括后续带来的乘法和除法都拥有同一套类似的处理逻辑,仅仅是在核心的运算逻辑编写上有一些差异。
因此,接下来,我将基于高精度加法讲述的思想,进一步讲述高精度减法的逻辑实现以及代码编写。
二、问题分析
1、需求重述
为实现基本数据类型无法实现的相减运算,实现一个算法,能够计算更高位数的减法运算。
2、问题分析
基于上一个高精度加法的讲述,我们可以了解到这种高精度运算的基本思路如下:
1) 构建字符数组存放两个数的各个位上的数;
2)将字符数组转化为整形数组,并且为方便编码,我们逆序存放;
3)编写具体计算逻辑,注意特殊情况处理(如加法要注意进位的处理,减法注意借位等):
1 其中,要大致判断好结果数组存放的容量;
2 编写运算规则。
4)进一步完善实际结果数组长度(即实际结果的位数大小);
5)最后,逆序进行输出即可得到结果。
3、思路构建与各部分代码实现
由上述流程我们不难发现,高精度的加减法差别就在于第三个流程以及第四个流程,其他的都是基本固定不变的代码,因此,现在我们简单讲述一下在核心的步骤三之前的思路与代码实现。
1)构建字符数组:
直接定义两个字符数组,并注意引入string头文件,利用gentline()函数输入数字存放至两个字符数组中,并定义好记录长度的变量分别存放两个字符数组长度,即数的位数大小。
具体C++代码实现如下:
string str1, str2; //定义两个字符数组
getline(cin, str1); //输入数字存放至数组中
int len1 = str1.size(); //记录数组长度,即数字位数
getline(cin, str2);
int len2 = str2.size();
2)数组类型转换,并逆序存放
定义三个合适大小的整形数组并记得初始化为0作为存放两个数以及结果数的数组,接着利用 str[i] - '0' 的方式将字符转化为整型并逆序存放至相应的整形数组中。
具体C++代码实现如下:
int n1[1000] = { 0 }, n2[1000] = { 0 }, sum[2000] = { 0 }, i;
for (i = 0; i < len1; i++)
{
n1[len1 - i - 1] = str1[i] - '0';
}
for (i = 0; i < len2; i++)
{
n2[len2 - i - 1] = str2[i] - '0';
}
3)核心步骤:减法计算逻辑实现
1)大致确定结果数的位数大小,确定结果数组容量:
我们先思考,两个非负数相减,得到的差只会比这两个数小,意味着最终结果的位数最大也是这两个数中最大的一个,换句话说,如果两数都是四位数,那么相减以后的结果最大也是四位数,还有可能是三位数、两位数甚至一位数。因此,我们可以大致确定存放结果数组的容量为两个数中数组长度最大的那一个,即找出存放两个数的数组长度最大值作为结果数组的容量,便于确定遍历条件。
具体代码实现如下:
int maxlen = len1;
//三目运算符 exp1?exp2:exp3
//表示如果exp1,则exp2,否则exp3
len1 < len2 ? maxlen = len2 : len1;
2) 编写减法运算规则,处理借位
现在,我们来编写减法运算,和加法类似,首先我们肯定是对两个数作减法(即n1[i]-n2[i]),其次,我们就要假设如果某个位上的数相减后发生需要借位,那会是什么情况?不妨我们试试比如34-28,现在对个位上的数做减法,4-8很明显,减不够然后要借一,就成了4+10-8=6,然后借了位的那个数就要-1,以此类推,我们就很容易理解了:
如果某位上的数相减为负则说明需要借位,然后+10,同时需要借位的话被借位的数就要-1,然后通过循环即可实现完整的减法运算了;反之,不需要借位即相减大于等于0,则直接相减就完事可以进行下一个位上的减法操作了。
接下来是代码实现:
for (i = 0; i < maxlen; i++)
{
//减法运算及借位处理
if (n1[i] - n2[i] < 0) //小于0则需要借位
{
sum[i] += n1[i] - n2[i] + 10; //借1以后就+10
sum[i + 1]--; //被借位以后需要-1
}
else
{
sum[i] += n1[i] - n2[i]; //若相减大于零则直接进行即可
}
}
4)进一步完善存放结果的数位大小
前面已经计算了减法得出了结果,现在我们在输出前需要做最后一步完善,就是进一步确定结果的数组长度,因为前面说了,减法的结果位数最多和两数的位数最大值相同,因此如果减数很大的话,可能求出的数会位数更小。
所以,现在我们就是要消除这个没有用的空间,如何判断呢?由于前面我们对数组都进行了初始化0的操作,所以很容易看出如果说在容量范围内,如果末尾存放的仍为0则说明并未利用到,因为我们数组是把数字逆序存放的也就是末尾都是数字的最高位,不可能为0的。所以一旦发现存放结果的数组末尾仍是0,则说明肯定是没有占用,因此此时我们就清调这部分即将数组长度--即可。
具体实现如下:
if (sum[maxlen - 1] == 0 && maxlen > 0) maxlen--;
5)最后,逆序输出即可得到结果
前面的准备工作都做好了,接下来只需要简单的遍历输出即可,就注意一下需要逆序,否则输出的是从个位开始的数。
具体实现如下:
//4、最后逆序输出最终的数组即可
for (int i = 0; i < maxlen; i++)
{
cout << sum[maxlen - 1 - i];
}
三、总代码实现
#include<iostream>
#include<string>
using namespace std;
//高精度减法
//区别在于减法会发生借位处理
int main()
{
//1、利用两个字符数组分别存放两个高位数
string str1, str2;
int n1[1000] = { 0 }, n2[1000] = { 0 }, sum[2000] = { 0 }, i;
getline(cin, str1);
int len1 = str1.size();
getline(cin, str2);
int len2 = str2.size();
//2、将两个字符数组中逆序并转化成整形数组
for (i = 0; i < len1; i++)
{
n1[len1 - i - 1] = str1[i] - '0';
}
for (i = 0; i < len2; i++)
{
n2[len2 - i - 1] = str2[i] - '0';
}
//两数相减,差值位数最高为两数中位数最大的位数
int maxlen = len1;
len1 < len2 ? maxlen = len2 : len1;
//3、分别对应相减后将各个结果存放至领一个新的数组
for (i = 0; i < maxlen; i++)
{
//减法运算及借位处理
if (n1[i] - n2[i] < 0)
{
sum[i] += n1[i] - n2[i] + 10;
sum[i + 1]--;
}
else
{
sum[i] += n1[i] - n2[i];
}
}
//如果最高位没有进位填补,即仍为0,则我们需要清掉这个空间,避免输出最高位为0的情况
if (sum[maxlen - 1] == 0 && maxlen > 0) maxlen--;
//4、最后逆序输出最终的数组即可
for (int i = 0; i < maxlen; i++)
{
cout << sum[maxlen - 1 - i];
}
return 0;
}
以上就是我个人的一些分享了,值得注意的是,该高精度加法的讲述重点在于算法的实现方式,故采用的是最基础的情况,如有更高要求的需要,可理解后自行对代码进行相关的优化,谢谢!