算法基础1.3.2高精度减法
前言
先看高精度加法的文章,如果没有看,我把高精度加法文章中的总结前言放到这里
该文章探讨的高精度代指C++中极大整数的计算,不是浮点数(y总说那个少见,不讲)。
这个问题只在C++中存在,Java有大整数类来解决,python本身特性就已经解决了。
高精度整数分为四种类型:A+B,A-B,A*a(一个大数乘一个小数),A / a(一个小数除一个大数)。这里面的大数(大写字母)极端一点的话,它的位数能来到10的6次方。小数(小写字母)他的数值一般是10000。
对于高精度整数,我们的存储方式一般是数组,每一个数组位置保存一个数。
下文我们都是使用的C++中的
vector
而非数组array
,使用数组也可以,但是vector
提供的方法会更加便利。比如我们得到内容长度,vector
可以直接使用他的函数length()
,而数组则需要我们利用sizeof(a)/sizeof(a[0])
去计算。正文开始之前我们再谈一下四个算法的共性:
- 数据存储方面,我们是把个位存在0位置,十位存在1位置....这虽然相当于让我们的数倒过来了,但是目的是相当有用的。如果在进行计算时我们这个数要进位,那么比起把所有数往后面挪一位再把进位的数放到第0位,肯定是直接把进位的数加到数组最后一位更加轻松,同时运算也更快
- 思路方面,其实就是人工模拟计算过程,跟我们在演草纸上计算的思路一样。
在学会高精度加法后,我们就已经将四个算法的整体架构学会了,我们的存储方式都是一样的。我们需要改动的只有函数的内容,而指导思想都是一样的,就是使用代码人工模拟算法计算过程。
正文
这里有几个新的思想
- 我们始终保持被减数大于减数,这样可以让我们的运算思路唯一。面对结果为负的情况(此时被减数小于减数),我们就让两者减法顺序互换,保证函数运算结果为正值,然后在最后的输出中输出一个
-
即可 - 就像上面加法一样,我们对于减法思路也可以进行拆解:当前位的值 = 被减数当前位的值 - 减数当前位的值 - 上一位借的1(如果有的话)
注意:下面的代码只考虑了两个数都是正数的情况,对于其他情况要使用其他模板。因为对于任意两个数相减,我们都可以换成两个数的绝对值相减(同号,并且同负号情况下需要整体再加一个符号或者将他们的位置反转),或者两个数的绝对值相加(异号,同时其中一种情况要整体加一个负号)
#include <iostream>
#include <vector>
using namespace std;
// 比较两个数的大小,保证被减数大于减数。
bool cmp(vector<int> &A, vector<int> &B)
{
//先比较两个数的位数,如果位数不同可以直接得出比大小的答案
if (A.size() != B.size()) return A.size() > B.size();
//如果位数一样,再从最大位数开始一个数一个数的比较
for (int i = A.size() - 1; i >= 0; i -- )
if (A[i] != B[i])
return A[i] > B[i];
return true;
}
// 进行减法
vector<int> sub(vector<int> &A, vector<int> &B)
{
vector<int> C;
for (int i = 0, t = 0; i < A.size(); i ++ )
{
// 下面两行代码将A[i]-B[i]-t进行了拆分(因为-B[i]这个要分情况讨论)
t = A[i] - t;
if (i < B.size()) t -= B[i];
// 这个其实是将两种情况整合了,具体哪两种情况,看下面的解释
C.push_back((t + 10) % 10);
// 计算下一位是否被此位借1了
if (t < 0) t = 1;
else t = 0;
}
//把前置0都去掉,比如003这种。同时保证答案为0时不去掉
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
int main()
{
string a, b;
vector<int> A, B;
cin >> a >> b;
for (int i = a.size() - 1; i >= 0; i -- ) A.push_back(a[i] - '0');
for (int i = b.size() - 1; i >= 0; i -- ) B.push_back(b[i] - '0');
vector<int> C;
// 调整两个数关于除数与被除数的关系
if (cmp(A, B)) C = sub(A, B);
else C = sub(B, A), cout << '-';
for (int i = C.size() - 1; i >= 0; i -- ) cout << C[i];
cout << endl;
return 0;
}
现在我们解释一下函数里对t
的使用,这个t
既作为当前位结果的一个记录,又作为借位的标记来传递给下一次循环(进入下一次循环前t
=1说明下一位被当前借了1)
- 我们一开始将
t
初始化为0(在for
循环语句中),这是因为个位数上的借位情况一定是没有被借位(t
值为0) - 后面我们使用的
t = A[i] - t;if (i < B.size()) t -= B[i];
便是遵循我们的减法思路。其实这里可以开个新的变量来存储当前值,但是使用t
就够了。 - 算出当前值
t
后,如果- 为正,说明不需要借位,这个位置上的数就是
t
本身 - 为负,说明经过减法后欠了一些数,需要找下一位借1,借完1后当前位的数就是
t+10
(这属于小学就知道的思想,只不过放代码里了) - 而我们的
(t + 10) % 10
就巧妙地将两个情况结合在了一起
- 为正,说明不需要借位,这个位置上的数就是
- 注意上面放入容器中的操作我们并没有改变
t
的值,他现在还是有正负的。我们此时根据他的符号就可以判断它有没有找下一位借1- 如果借了(负值),那么
t
变为1。在下次循环中,他会给下一位的数减一 - 如果没借(正值),那么
t
变为0,在下一次循环中对结果没有影响
- 如果借了(负值),那么