C++的高精度减法
为什么需要高精度计算
对于 C++ 而言,最大的数据为 long long(64b,8位),对于超过 8B 的数据,C++ 没有对应的数据类型进行表示。所以我们需要知道高精度计算。更详细的解释,可以参考这个网页https://blog.csdn.net/justidle/article/details/104414459。
高精度减法计算原理
在读小学时,我们做减法都采用竖式方法,如图 1 所示。 这样,我们可以写出两个整数相减的算法。
我们就可以用 C++ 语言来模拟这个竖式减法的过程。我们可以考虑利用 C++ 的数组来存储对应数据,假设用数组 A 存储被减数 856 的每一位,具体来说就是 A1 存储个位 6,A2 存储十位 5,A3存储百位 8;类似数组 A 的结构,使用数组 B 存储减数 257;类似数组 A 的结构,使用数组 C 来存储对应的差 599。两数相加的结果就如图 2 所示。这样理论上来说,我们就可以计算无限大的数据。如上图 2 所示,下表表示对应的存储方式。
数组 A | 数组 B | 数组 C | |
[0] | 6 | 7 | 9 |
[1] | 5 | 5 | 9 |
[2] | 8 | 2 | 5 |
总结:利用数组存储,突破存储的限制。每个位置存储 0 ~ 9 之间的数据。
高精度减法实现
思路
1、定义存储数组。
2、被减数和减数确认。由于减法可能出现负数。
3、读入数据到数组中。注意:保证被减数大于减数;倒序存放,也就是个位放在数组下标为 0 的地方。
4、从个位开始模拟竖式加法的过程,完成整个减法。
5、删除前导 0 。所谓前导零,就是出现类似这样数据 01234,这个 0 实际是不需要的。
6、输出减法的结果。倒序输出减法的结果数组 C,因为我们的个位是存储在下标为 0 的地方。
技术细节说明
定义存储数组
根据题目的要求定义数组。这个部分代码如下:
const int MAXN = 1e5+4; //根据题目的最大值。+4为了防止A+B出现进位
char s1[MAXN] = {};//存储字符串
char s2[MAXN] = {};//存储字符串
char tmp[MAXN] = {};//交换用字符串
int a[MAXN] = {};//存储加数A
int b[MAXN] = {};//存储加数B
int c[MAXN] = {};//存储和B
被减数和减数确认
由于减法可能出现负数,如 3-5=-2,我们在计算的时候,实际是使用 5-3=2,最后在结果前面添加负号。如果出现被减数小于减数的情况,要将两者颠倒。
scanf("%s %s", s1, s2);//读入字符串
int lena = strlen(s1);
int lenb = strlen(s2);
//判断最终的结果符号
if ((lena<lenb) || (lena==lenb && strcmp(s1,s2)<0)) {
//被减数小于减数,结果为负数
printf("-");
//交换数据
strcpy(tmp, s1);
strcpy(s1, s2);
strcpy(s2, tmp);
//更新长度数据
lena = strlen(s1);
lenb = strlen(s2);
}
读入数据到数组
利用读入字符串的方法读入数据,再倒序写入到对应的数组中。这个部分代码如下:
//将字符串写入到数组A中
for (int i=0; i<lena; i++) {
//倒序写入
a[i] = s1[lena-i-1] - '0';
}
//将字符串写入到数组B中
for (int i=0; i<lenb; i++) {
//倒序写入
b[i] = s2[lenb-i-1] - '0';
}
模拟竖式减法
有两个技术细节:如何判断发生借位。这个部分代码如下:
//模拟竖式减法
for (int i=0; i<lena; i++) {
if (a[i]<b[i]) {
//有借位
a[i+1]--;
a[i] += 10;
}
c[i] = a[i] - b[i];
}
删除前导零
因为减法运算可能会出现最高位为零,所以我们需要判断是否需要删除前导零。这个部分代码如下:
//删除前导零
for (int i=lena-1; i>=0; i--) {
//因为我们是从索引 0 开始,所以最高位是保存在 len-1
if (0==c[i] && lena>1) {
//注意要有 lena>1 这个条件。考虑特殊情况,加法结果为 00,我们实际要输出 0。
lena--;
} else {
//第一个不是零的最高位,结束删除
break;
}
}
输出计算结果
采用倒序的方式输出,因为我们数据保存是倒序结构,也就是低位在前。
//逆序打印输出
for (int i=lena-1; i>=0; i--) {
printf("%d", c[i]);
}
printf("\n");
例题和 AC 代码
题目
题目链接
一本通 OJ:http://ybt.ssoier.cn:8088/problem_show.php?pid=1169。
我自己 OJ:http://47.110.135.197/problem.php?id=1216。
题目描述
求两个大的正整数相减的差。
输入
共 2 行,第 1 行是被减数 a,第 2 行是减数 b,不保证 a > b。每个大整数不超过 10005 位。
输出
一行,即所求的差。
样例输入
9999999999999999999999999999999999999
9999999999999
样例输出
9999999999999999999999990000000000000
分析
题目告诉我们不超过 200 位,也就是 MAXN = 10005+4。
AC 代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e4+4; //根据题目的最大值。+4为了防止A+B出现进位
char s1[MAXN] = {};//存储字符串
char s2[MAXN] = {};//存储字符串
char tmp[MAXN] = {};//交换用字符串
int a[MAXN] = {};//存储加数A
int b[MAXN] = {};//存储加数B
int c[MAXN] = {};//存储和B
int main() {
scanf("%s %s", s1, s2);//读入字符串
int lena = strlen(s1);
int lenb = strlen(s2);
//判断最终的结果符号
if ((lena<lenb) || (lena==lenb && strcmp(s1,s2)<0)) {
//被减数小于减数,结果为负数
printf("-");
//交换数据
strcpy(tmp, s1);
strcpy(s1, s2);
strcpy(s2, tmp);
//更新长度数据
lena = strlen(s1);
lenb = strlen(s2);
}
//将字符串写入到数组A中
for (int i=0; i<lena; i++) {
//倒序写入
a[i] = s1[lena-i-1] - '0';
}
//将字符串写入到数组B中
for (int i=0; i<lenb; i++) {
//倒序写入
b[i] = s2[lenb-i-1] - '0';
}
//模拟竖式减法
for (int i=0; i<lena; i++) {
if (a[i]<b[i]) {
//有借位
a[i+1]--;
a[i] += 10;
}
c[i] = a[i] - b[i];
}
//删除前导零
for (int i=lena-1; i>=0; i--) {
//因为我们是从索引 0 开始,所以最高位是保存在 len-1
if (0==c[i] && lena>1) {
//注意要有 lena>1 这个条件。考虑特殊情况,加法结果为 00,我们实际要输出 0。
lena--;
} else {
//第一个不是零的最高位,结束删除
break;
}
}
//逆序打印输出
for (int i=lena-1; i>=0; i--) {
printf("%d", c[i]);
}
printf("\n");
return 0;
}