【上交ACM-算法初级】高精度运算
高精度加减法
- 高精度整数可以由数位数组和长度两部分组成。数位数组存储整数时使用的是小端序。
使用小端序的理由:
因为加法、减法及后面介绍的乘法等,都是从低位算到高位。这样存储符合我们平时习惯的枚举顺序。
因为数位计算结束后,需要更新数位数组的长度。把高位放在数组后面比较方便数组伸缩。
-
高精度整数使用字符串输入。由于存储顺序和打印顺序不一致,所以输入输出时都需要翻转操作。
-
高精度加法、减法和乘法都可分为数位操作和维护长度两部分。维护长度时,注意不要将长度减小到0。
以高精度减法为例:
- 数位操作:
根据减法竖式计算的规则:从低位开始,逐位相减,若该位不够减,需向下一位借位,并且借一当十。
上面的例子中,可以发现,第
i
位的结果等于被减数第i
位减去减数第i
位和低位的借位。
- 维护长度在两个数相减时,若我们另初始长度为被减数的长度。
可以想象,最终计算的差的长度,和被减数相比,很可能变小。
- 代码实现
这里,我们总假设被减数大于等于减数。
下面的代码依旧分成数位操作、维护长度和输出三个部分。
#include <bits/stdc++.h> #define N 110 using namespace std; // 同样,这里采用小端序存储 int a_digits[N] = {8, 6, 3, 2}, a_len = 4; int b_digits[N] = {7, 9, 9}, b_len = 3; int ans_digits[N], ans_len; int main() { // 1. 数位操作 // 我们依旧是从低位到高位开始逐位相减 // 因为我们总假设a>=b,所以初始长度先设为a的长度 // 考虑每一位,需要计算的部分是被减数的当前位,减去减数的当前位,再减去低位的借位 // 如果上一步的计算得出当前位<0,那我们需要向高位借位,然后给当前位+10 ans_len = a_len; // 初始长度 int k = 0; // 维护借位 for (int i = 0; i < ans_len; ++i) { ans_digits[i] = a_digits[i] - b_digits[i] - k; if (ans_digits[i] < 0) { k = 1; ans_digits[i] += 10; } else k = 0; // 这里赋值成0很关键,而且容易遗漏 } // 2. 维护长度 // 想象一下,如果实际数字是1,但是长度记录是4的话,那么输出该数字结果将是0001, // 也就是出现了“前导0”,所以维护长度的目的是为了去掉前导0 // 所以,我们用while循环实现这样的逻辑:只要最高位是0,我们就把位数缩小1位。 // 但是需要注意,只有位数>1的时候才可以缩小,否则当保存的数字是0时,长度也会减为0. while (ans_len > 1 && !ans_digits[ans_len - 1]) // 只有长度大于1才可以去掉前导零 --ans_len; // 3. 输出 for (int i = ans_len - 1; i >= 0; --i) cout << ans_digits[i]; cout << endl; return 0; }
使用小端序的好处
回顾一下小端序的存储方式:数字的低位在地址的低位。由此,我们可以看到使用小端序的理由:
- 因为加法、减法以及后面介绍的乘法等,都是从低位算到高位。这样存储符合我们平时习惯的枚举顺序。
- 因为数位计算结束后,需要更新数位数组的长度。把高位放在数组后面比较方便数组伸缩。
示例代码:
#include <bits/stdc++.h> #define N 110 using namespace std; int a_digits[N] = {3, 2}, a_len = 2; int b_digits[N] = {8, 6}, b_len = 2; // int a_digits[N] = {0}, a_len = 1; // int b_digits[N] = {9, 9}, b_len = 2; int ans_digits[N * 2], ans_len; int main() { // 1. 数位操作 // 考虑到(a位数×b位数)最多得到(a + b)位数,所以我们设置答案初始长度为a + b。 // 另外考虑到第i位×第j位会贡献到(i + j)位,所以,我们用累加的方式计算答案的每一位。 // 值得注意的是,这里累加的结果可能>=10,所以按理说应该进位,但为了效率考虑,我们 // 在后面统一维护进位,而不是一边加一边进。 ans_len = a_len + b_len; // 初始化长度 for (int i = 0; i < ans_len; ++i) ans_digits[i] = 0; // 因为是不断累加的形式,所以要将范围内的元素初始化为0。 for (int i = 0; i < a_len; ++i) for (int j = 0; j < b_len; ++j) ans_digits[i + j] += a_digits[i] * b_digits[j]; // ans的每一位更新都要使用累加的形式,这是因为对于ans的第k位,满足i + j == k的(i, j)很多,所以可能答案的第k位可能先后被更新很多次。 // 2. 统一进位 // 上一步提到,因为累加后得到的答案各个数位有可能>=10,所以要将其变成一个合法的高精度形式 // 也就是说,要把>=10的部分进位到下一位。所以我们用类似于高精度加法的方法维护。 // 每一位只需要将自己的值和低位的进位相加,然后把>=10的部分作为新的进位进到下一位。 int k = 0; for (int i = 0; i < ans_len; ++i) { ans_digits[i] += k; k = ans_digits[i] / 10; ans_digits[i] %= 10; } // 3. 维护长度 // 上面提到,(a位数×b位数)最多得到(a + b)位数 // 但考虑一个非零整数和0相乘的情况,答案的长度很可能降为1。所以我们需要向减法一样更新长度。 // 只有当长度仍然>1的时候,才需要去掉前导0 while (ans_len > 1 && ans_digits[ans_len - 1] == 0) --ans_len; // 4. 输出 for (int i = ans_len - 1; i >= 0; --i) cout << ans_digits[i]; cout << endl; return 0; }