高精度算法
什么是高精度?为什么需要高精度?
C++中,整数类型(如 int
、long long
)的存储大小是固定的:
int
:通常占4字节(32位),能表示的最大值为 2147483647(约 (2^31-1)),超过这个值会发生溢出(结果错误)。long long
:占8字节(64位),最大值为 9223372036854775807(约 (2^63-1)),但对于更大的数(如 (10^20))仍无法存储。
示例:
此时 b
的值会错误地变为负数或其他数值,无法得到正确结果。
因此,当数值超过 long long
范围时,我们用 数组或字符串 存储每一位数字,模拟竖式计算过程。
这样的模拟过程,叫做高精度算法。
高精度问题包含很多小的细节,实现上也有很多讲究。
所以今天就来一起实现一个简单的计算器吧。
计算器问题
输入:一个形如 a <op> b
的表达式。
a
、b
分别是长度不超过 的十进制非负整数;<op>
是一个字符(+
、-
、*
或/
),表示运算。- 整数与运算符之间由一个空格分隔。
输出:运算结果。
- 对于
+
、-
、*
运算,输出一行表示结果; - 对于
/
运算,输出两行分别表示商和余数。 - 保证结果均为非负整数。
存储
在平常的实现中,高精度数字利用字符串表示,每一个字符表示数字的一个十进制位。因此可以说,高精度数值计算实际上是一种特别的字符串处理。
读入字符串时,数字最高位在字符串首(下标小的位置)。但是习惯上,下标最小的位置存放的是数字的 最低位,即存储反转的字符串。这么做的原因在于,数字的长度可能发生变化,但我们希望同样权值位始终保持对齐(例如,希望所有的个位都在下标 [0]
,所有的十位都在下标 [1]
……);同时,加、减、乘的运算一般都从个位开始进行(回想小学的竖式运算),这都给了「反转存储」以充分的理由。
此后我们将一直沿用这一约定。定义一个常数 LEN = 1004
表示程序所容纳的最大长度。
由此不难写出读入高精度数字的代码:
示例代码
输出也按照存储的逆序输出。由于不希望输出前导零,故这里从最高位开始向下寻找第一个非零位,从此处开始输出;终止条件 i >= 1
而不是 i >= 0
是因为当整个数字等于 时仍希望输出一个字符 0
。
示例代码
拼起来就是一个完整的复读机程序。
示例代码
四则运算
四则运算中难度也各不相同。最简单的是高精度加减法,其次是高精度—单精度(普通的 int
)乘法和高精度—高精度乘法,最后是高精度—高精度除法。
我们将按这个顺序分别实现所有要求的功能。
加法
高精度加法,其实就是模拟竖式加法。
也就是从最低位开始,将两个加数对应位置上的数码相加,并判断是否达到或超过 。如果达到,那么处理进位:将更高一位的结果上增加 ,当前位的结果减少 。

示例代码
试着和上一部分结合,可以得到一个加法计算器。
示例代码
减法
高精度减法,也就是竖式减法。
从个位起逐位相减,遇到负的情况则向上一位借 。整体思路与加法完全一致。
示例代码
将上一个程序中的 add()
替换成 sub()
,就有了一个减法计算器。
示例代码
试一试,输入 1 2
——输出 /9999999
,发生了错误。
事实上,上面的代码只能处理减数 大于等于被减数 的情况。处理被减数比减数小,即 时的情况很简单。
要计算 的值,因为有 ,可以调用以上代码中的 sub
函数,写法为 sub(b,a,c)
。要得到 的值,在得数前加上负号即可。
乘法
高精度—单精度
先考虑一个简单的情况:乘数中的一个是普通的 int
类型。有没有简单的处理方法呢?
一个直观的思路是直接将 每一位上的数字乘以 。从数值上来说,这个方法是正确的,但它并不符合十进制表示法,因此需要将它重新整理成正常的样子。
重整的方式,也是从个位开始逐位向上处理进位。但是这里的进位可能非常大,甚至远大于 ,因为每一位被乘上之后都可能达到 的数量级。所以这里的进位不能再简单地进行 运算,而是要通过除以 的商以及余数计算。详见代码注释,也可以参考下图展示的一个计算高精度数 乘以单精度数 的过程。

当然,也是出于这个原因,这个方法需要特别关注乘数 的范围。若它和 (或相应整型的取值上界)属于同一数量级,那么需要慎用高精度—单精度乘法。
示例代码
高精度—高精度
如果两个乘数都是高精度,那么竖式乘法又可以大显身手了。
回想竖式乘法的每一步,实际上是计算了若干 的和。例如计算 ,计算的就是 。
于是可以将 分解为它的所有数码,其中每个数码都是单精度数,将它们分别与 相乘,再向左移动到各自的位置上相加即得答案。当然,最后也需要用与上例相同的方式处理进位。
注意这个过程与竖式乘法不尽相同,我们的算法在每一步乘的过程中并不进位,而是将所有的结果保留在对应的位置上,到最后再统一处理进位,但这不会影响结果。

示例代码
除法
高精度除法的一种实现方式就是竖式长除法。
竖式长除法实际上可以看作一个逐次减法的过程。例如上图中商数十位的计算可以这样理解:将 减去三次 后变得小于 ,不能再减,故此位为 。

为了减少冗余运算,我们提前得到被除数的长度 与除数的长度 ,从下标 开始,从高位到低位来计算商。这和手工计算时将第一次乘法的最高位与被除数最高位对齐的做法是一样的。
参考程序实现了一个函数 greater_eq()
用于判断被除数以下标 last_dg
为最低位,是否可以再减去除数而保持非负。此后对于商的每一位,不断调用 greater_eq()
,并在成立的时候用高精度减法从余数中减去除数,也即模拟了竖式除法的过程。
示例代码
入门篇完成!
将上面介绍的四则运算的实现结合,即可完成开头提到的计算器程序。
示例代码