为什么需要高精度计算
对于 C++ 而言,最大的数据为 long long(64b,8位),对于超过 8B 的数据,C++ 没有对应的数据类型进行表示。所以我们需要知道高精度计算。更详细的解释,可以参考这个网页https://blog.youkuaiyun.com/justidle/article/details/104414459。
高精度乘法计算原理
在读小学时,我们做乘法都采用竖式方法,如图 1 所示。 这样,我们可以写出两个整数相减的算法。
我们就可以用 C++ 语言来模拟这个竖式减法的过程。我们可以考虑利用 C++ 的数组来存储对应数据,假设用数组 A 存储被减数 856 的每一位,具体来说就是 A1 存储个位 6,A2 存储十位 5,A3存储百位 8;类似数组 A 的结构,使用数组 B 存储减数 25;类似数组 A 的结构,使用数组 C 来存储对应的乘积 21400。两数相加的结果就如图 2 所示。这样理论上来说,我们就可以计算无限大的数据。如上图 2 所示,下表表示对应的存储方式。
数组 A | 数组 B | 数组 C | |
[0] | 6 | 5 | 0 |
[1] | 5 | 2 | 0 |
[2] | 8 | 4 | |
[3] | 1 | ||
[4] | 2 |
如上面的图 2 ,首先计算被乘数与乘数的个位数字的乘积,把结果保存到积数组中,然后再用被除数乘以乘数的十位数字,把结果退一位加到积数组中。每加一次乘积结果就进行一次进位处理,放在一个新组数里面。其方法与加法中的进位处理一样。
关键点
1、a[i]*b[j] 应该累加到 i+j 的位置;
2、累加总结果为: c[i+j] = a[i]*b[j]+jw+c[i+j];
3、所以每次累加完后:
1)带入下一位累加的进位: jw = c[i+j] / 10;
2)本位实际数: c[i+j] %= 10。
高精度乘法实现
思路
1、定义存储数组。
2、读入数据处理。
3、从个位开始模拟竖式乘法的过程,完成整个乘法。
4、删除前导 0 。所谓前导零,就是出现类似这样数据 01234,这个 0 实际是不需要的。
5、输出结果。倒序输出减法的结果数组 C,因为我们的个位是存储在下标为 0 的地方。
技术细节说明
定义存储数组
根据题目的要求定义数组。这个部分代码如下:
const int MAXN = 1e5+4; //根据题目的最大值。+4为了防止A+B出现进位
char s1[MAXN] = {};//存储字符串
char s2[MAXN] = {};//存储字符串
int a[MAXN] = {};//存储加数A
int b[MAXN] = {};//存储加数B
int c[2*MAXN] = {};//存储乘积
读入数据并处理
这里我们先考虑以下几种情况,即 -3*5=-15 或者 -4*8=-32 或者 -3*(-2)=6,也就是说,需要对输入数据的正负号进行判断。这个部分代码如下:
scanf("%s %s", s1, s2);//读入字符串
//处理负数
bool flaga = false;//乘数a的符号
if ('-'==s1[0]) {
flaga = true;
strcpy(s1, &s1[1]);//删除负号
}
bool flagb = false;//乘数b的符号
if ('-'==s2[0]) {
flagb = true;
strcpy(s2, &s2[1]);//删除负号
}
//处理输出的负号
if ((true==flaga && false==flagb) || (false==flaga && true==flagb)) {
printf("-");
}
//处理乘数1
int lena = strlen(s1);
for (int i=0; i<lena; i++) {
a[lena-i-1]=s1[i]-'0';
}
//处理乘数2
int lenb = strlen(s2);
for (int i=0; i<lenb; i++) {
b[lenb-i-1]=s2[i]-'0';
}
模拟竖式乘法
这个部分代码如下:
//模拟竖式乘法
int jw;//上一轮计算进位
for (int i=0; i<lena; i++) {
jw=0;
for (int j=0; j<lenb; j++) {
//交叉乘积
c[i+j] = a[i]*b[j]+jw+c[i+j];//当前乘积+上次乘积进位+原数
jw = c[i+j]/10;//处理进位
c[i+j] %= 10;
}
c[i+lenb]=jw;//进位设置
}
删除前导零
这个部分代码如下:
//删除前导零
int lenc=lena+lenb;
for (int i=lenc-1; i>=0; i--) {
//因为我们是从索引 0 开始,所以最高位是保存在 len-1
if (0==c[i] && lenc>1) {
//注意要有 lenc>1 这个条件。考虑特殊情况,加法结果为 00,我们实际要输出 0。
lenc--;
} else {
//第一个不是零的最高位,结束删除
break;
}
}
输出计算结果
采用倒序的方式输出,因为我们数据保存是倒序结构,也就是低位在前。
//逆序打印输出
for (int i=lenc-1; i>=0; i--) {
printf("%d", c[i]);
}
printf("\n");
例题和 AC 代码
题目
题目链接
一本通 OJ:http://ybt.ssoier.cn:8088/problem_show.php?pid=1174,或者http://ybt.ssoier.cn:8088/problem_show.php?pid=1307。
我自己 OJ:http://47.110.135.197/problem.php?id=1034。
题目描述
求两个不超过 200 位的非负整数的积。
输入
有两行,每行是一个不超过 200 位的非负整数,没有多余的前导 0。
输出
一行,即相乘后的结果。结果里不能有多余的前导 0,即如果结果是 342,那么就不能输出为 0342。
样例输入
12345678900
98765432100
样例输出
1219326311126352690000
分析
题目告诉我们不超过 200 位,也就是 MAXN = 200+4。特别的地方要注意乘积的长度为 2*MAXN。
AC 代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200+4; //根据题目的最大值。+4为了防止A+B出现进位
char s1[MAXN] = {};//存储字符串
char s2[MAXN] = {};//存储字符串
int a[MAXN] = {};//存储加数A
int b[MAXN] = {};//存储加数B
int c[2*MAXN] = {};//存储和B
int main() {
scanf("%s %s", s1, s2);//读入字符串
//处理负数
bool flaga = false;//乘数a的符号
if ('-'==s1[0]) {
flaga = true;
strcpy(s1, &s1[1]);//删除负号
}
bool flagb = false;//乘数b的符号
if ('-'==s2[0]) {
flagb = true;
strcpy(s2, &s2[1]);//删除负号
}
//处理输出的负号
if ((true==flaga && false==flagb) || (false==flaga && true==flagb)) {
printf("-");
}
//处理乘数1
int lena = strlen(s1);
for (int i=0; i<lena; i++) {
a[lena-i-1]=s1[i]-'0';
}
//处理乘数2
int lenb = strlen(s2);
for (int i=0; i<lenb; i++) {
b[lenb-i-1]=s2[i]-'0';
}
//模拟竖式乘法
int jw;//上一轮计算进位
for (int i=0; i<lena; i++) {
jw=0;
for (int j=0; j<lenb; j++) {
//交叉乘积
c[i+j] = a[i]*b[j]+jw+c[i+j];//当前乘积+上次乘积进位+原数
jw = c[i+j]/10;//处理进位
c[i+j] %= 10;
}
c[i+lenb]=jw;//进位设置
}
//删除前导零
int lenc=lena+lenb;
for (int i=lenc-1; i>=0; i--) {
//因为我们是从索引 0 开始,所以最高位是保存在 len-1
if (0==c[i] && lenc>1) {
//注意要有 lenc>1 这个条件。考虑特殊情况,加法结果为 00,我们实际要输出 0。
lenc--;
} else {
//第一个不是零的最高位,结束删除
break;
}
}
//逆序打印输出
for (int i=lenc-1; i>=0; i--) {
printf("%d", c[i]);
}
printf("\n");
return 0;
}