1. 概述 #
高精度数的概念 #
整数和小数有计算精度的限制。比如:
- unsigned long long 的最大值: 1844 6744 0737 0955 1615 (一千八百多万亿)
- long double 的范围: 1.18973e+4932 ~ 3.3621e-4932
太大的数,或者小数点太多的数,在计算上有限制。就需要使用特殊的处理方法。这就称为高精度计算。在一般的科学计算中,会经常算到小数点后几百位或者更多,当然也可能是几千亿几百亿的大数字。一般这类数字我们统称为高精度数,高精度算法是用计算机对于超大数据的一种模拟加,减,乘,除,乘方,阶乘,开方等运算。对于非常庞大的数字无法在计算机中正常存储,于是,将这个数字拆开,拆成一位一位的,或者是四位四位的存储到一个数组中, 用一个数组去表示一个数字,这样这个数字就被称为是高精度数。
数据的接收和存贮 #
- 当输入的数很长时,可采用字符串方式输入。

这样可以输入位数很长的数,利用字符串函数和操作运算,将每一位数取出,存入数组中。
因为数组存放的元素顺序与我们计算的顺序是相反的,在竖式计算中我们是将其右对齐(个位对个位,十位对十位,以此类推),而读取数字后的两个数组是左对齐的,因此我们要将里面的元素逆置。
请读入下面的数: 123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789 示例程序: void init() { string s; cin >> s; // 读入字符串 len = s.length(); for(i=0; i <= len; ++i) //逆置元素 a[i]=s[len-1] - '0'; // 将字符转化成数字 }
高精度数位数的确定 #
- 方法:接收时往往是用字符串,所以它的位数就等于字符串的长度。
进位,错位处理 #
下面例程中的 a b c 均为整形数组。
- 加法进位:
c[i] = a[i]+b[i]; if(c[i] >= 10) { c[i] %= 10; ++c[i+1]; }
- 减法借位:
if(a[i] < b[i]) { --a[i+1]; a[i]+=10; } c[i] = a[i]-b[i];
2. 高精度加法 #
请输入两个数,计算它们的和。
【算法分析】
竖式方法举例:962+93
【例程】
#include < iostream > #include < string > using namespace std; //高精度加法函数 void add(int a[], int a_len, int b[], int b_len, int* result, int& result_len) { int i = 0, x = 0; result_len = 0; int max_a_b = a_len > b_len ? a_len : b_len; while (i< max_a_b) { int sub_a = i < a_len ? a[i] : 0; //判断i是否超过数组a的长度,若超过该位为0 int sub_b = i < b_len ? b[i] : 0; //判断i是否超过数组b的长度,若超过该位为0 result[i] = sub_a + sub_b + x;//逐位相加 x = result[i] / 10; result[i] %= 10; result_len++; i++; } if (x != 0) { result[i] = x; ++result_len; } } int main() { string astr, bstr; cin >> astr; cin >> bstr; int lena = astr.size(), lenb = bstr.size(); int a[100], b[100]; for (int i = 0; i< lena; ++i) a[lena - i - 1] = astr[i] - '0'; // 将数字倒过来,并转化成数字 for (int i = 0; i< lenb; ++i) b[lenb - i - 1] = bstr[i] - '0'; int c[100] = { 0 }, lenc=0; add(a, lena, b, lenb, c, lenc); // 计算 for (int i = 0; i < lenc; ++i) { cout << c[lenc - i - 1]; // 再将数字倒过来输出 } cout << endl; return 0; }
3. 高精度乘法 #
进位和错位处理:高精度
单精度 #

先考虑一个简单的情况:乘数中的一个是普通的 int 类型。有没有简单的处理方法呢?
一个直观的思路是直接将a每一位上的数字乘以b。从数值上来说,这个方法是正确的,但它并不符合十进制表示法,因此需要将它重新整理成正常的样子。
重整的方式,也是从个位开始逐位向上处理进位。但是这里的进位可能非常大,甚至远大于9,因为每一位被乘上之后都可能达到9b的数量级。所以这里的进位不能再简单地进行-10运算,而是要通过除以10的商以及余数计算。详见代码注释,也可以参考下图展示的一个计算高精度数1337乘以单精度数42的过程。
当然,也是出于这个原因,这个方法需要特别关注乘数b的范围。若它和 10^9(或相应整型的取值上界)属于同一数量级,那么需要慎用高精度—单精度乘法。
void mul_short(int a[], int b, int c[]) { clear(c); for (int i = 0; i < LEN - 1; ++i) { // 直接把 a 的第 i 位数码乘以乘数,加入结果 c[i] += a[i] * b; if (c[i] >= 10) { // 处理进位 // c[i] / 10 即除法的商数成为进位的增量值 c[i + 1] += c[i] / 10; // 而 c[i] % 10 即除法的余数成为在当前位留下的值 c[i] %= 10; } } }
进位和错位处理:高精度
高精度 #

如果两个乘数都是高精度,那么竖式乘法又可以大显身手。
于是可以将b分解为它的所有数码,其中每个数码都是单精度数,将它们分别与a相乘,再向左移动到各自的位置上相加即得答案。当然,最后也需要用与上例相同的方式处理进位。
注意这个过程与竖式乘法不尽相同,我们的算法在每一步乘的过程中并不进位,而是将所有的结果保留在对应的位置上,到最后再统一处理进位,但这不会影响结果。
void mul(int a[], int b[], int c[]) { clear(c); for (int i = 0; i < LEN - 1; ++i) { // 这里直接计算结果中的从低到高第 i 位,且一并处理了进位 // 第 i 次循环为 c[i] 加上了所有满足 p + q = i 的 a[p] 与 b[q] 的乘积之和 // 这样做的效果和直接进行上图的运算最后求和是一样的,只是更加简短的一种实现方式 for (int j = 0; j <= i; ++j) c[i] += a[j] * b[i - j]; if (c[i] >= 10) { c[i + 1] += c[i] / 10; c[i] %= 10; } } }
高精度乘法实现 #
请输入两个数,计算它们的积。
【算法分析】
竖式方法举例:
- 25*836
int b[] = {2,5}; int a[] = {8,3,6}; b1 b0 ___x_____a2_a1_a0__ c2 c1 c0 d2 d1 d0 ___e2_e1_e0________ f5 f4 f3 f2 f1 f0 我们不妨改一下数据的下标,以便于更好的理解下面的代码。 b1 b0 ___x_____a2_a1_a0__ c2 c1 c0 d3 d2 d1 ___e4_e3_e2________ f5 f4 f3 f2 f1 f0 ## 1 fi 的计算过程推导,是代码实现的关键。 1. f的序号和ab有什么关系?为什么是【f序号 = a序号 + b序号】 2. fi的值,是一个累加过程。 ## 2 两数乘积的位数,如何确定? f5 可能没有吗?
【例程】
#include < iostream > #include < string > using namespace std; void multiply(int a[], int b[], int a_len, int b_len, int* f, int& f_len) { int i = 0, x = 0; f_len = 0; for (int i = 0; i < a_len; ++i) { int x = 0; // x 表示进位 for (int j = 0; j < b_len; ++j) { // 乘法核心代码 f[i+j] += a[i] * b[j] + x; x = f[i+j] / 10; f[i+j] %= 10; } f[i + b_len] = x; } // 根据最后一位是否为 0 ,判断计算结果的位数 if (f[a_len + b_len - 1] == 0) f_len = a_len + b_len - 1; else f_len = a_len + b_len; } int main() { // 从屏幕获得输入 string astr, bstr; cin >> astr; cin >> bstr; int lena = astr.size(), lenb = bstr.size(); int a[100] = { 0 }, b[100] = { 0 }; // 假设数字的位数不超过 100 for (int i = 0; i < lena; ++i) a[lena - i - 1] = astr[i] - '0'; // 将数字倒过来 for (int i = 0; i < lenb; ++i) b[lenb - i - 1] = bstr[i] - '0'; // 计算高精度乘法 int f[100] = { 0 }, lenf(0); multiply(a, b, lena, lenb, f, lenf); // 必须是两个非负数,且 a 大于 b // 输出结果 for (int i = 0; i < lenf; ++i) { cout << f[lenf - i - 1]; } cout << endl; return 0; }
4. 高精度除法 #
高精度除以低精度 #
假设 a 是一个高精度整数,b是一个普通的整数。
【算法分析】
在高精度除以单精度时,从高位到低位逐位相除。最需要注意的问题是,后一位继承前一位的余数问题。
设高精度的数位数字为a[i],单精度数为b,第i+1位除b后的余数位r,把r加到i位时,应乘以进制X,即s=X*r+a[i].
- 注意位移处理即可
int b = 5; int a[] = {8,3,6}; (1) _1___ 5)836 _5___ 3 (2) _16__ 5)836 _5___ 33 _30__ 3 (3) _167__ 5)836 _5___ 33 _30__ 36 __35_ 1
【例程】
#include < iostream > #include < string > using namespace std; void assign(int a[], int a_len, int* dst, int& dst_len) { for (int i = 0; i < a_len; ++i) dst[i] = a[i]; dst_len = a_len; } void divide(int a[], int a_len, int b, int* result, int& result_len) { int c[100] = { 0 }, lenc = 0; // c 用于存放临时的计算结果 if (a[0] == 0) { result_len = 1; assign(c, 1, result, result_len); return; // 0是被除数,则商为0 } int i = 0, x = 0; result_len = 0; for (int i = 0; i < a_len; ++i) { c[i] = (x * 10 + a[i]) / b; x = (x * 10 + a[i]) % b; ++lenc; } // 去除前部可能出现的 0 int c_startID = 0; for (; c_startID < 100; ++c_startID) if (c[c_startID] != 0) break; for (int i = c_startID; i < lenc; ++i) { result[i - c_startID] = c[i]; ++result_len; } } int main() { // 从屏幕获得输入 被除数a和除数b均以字符串的形式输入 string astr, bstr; cin >> astr; cin >> bstr; //获得字符串a和b的长度也就是,a和b的位数 int lena = astr.size(), lenb = bstr.size(); //设置数组a[100]高精度数字a int a[100] = { 0 }, b = 0; for (int i = 0; i < lena; ++i) a[i] = astr[i] - '0'; // 除法不需要将数字倒过来 for (int i = 0; i < lenb; ++i) b += (bstr[i] - '0') * pow(10, lenb - i - 1); // 字符串转 10 进制数 //不能除以0 if (b == 0) return -1; // 设置数组c[100]存储计算结果,c[100]中存储的也是高精度的数字 int c[100] = { 0 }, lenc(0); divide(a, lena, b, c, lenc); // a 是被除数,是高精度值。b是低精度值,用 int 即可存储 // 输出 for (int i = 0; i < lenc; ++i) { cout << c[i]; // 正向输出 } cout << endl; return 0; }
高精度除以高精度 #
假设 a 是一个高精度整数,b 也是一个高精度整数。
【算法分析】
- 方法:使用减法模拟除法
将被除数,不断减去除数,直到余数小于除数。
- 1. 需要用到高精度减法
- 2. 需要用到高精度比较大小
- 3. 需要用到高精度加法
【例程】
#include < iostream > #include < string > using namespace std; int zero[] = { 0 }; int one[] = { 1 }; //赋值过程,将数组a[]中的数据赋值给b[],并将a的长度a_len赋值给b_len void assign(int a[], int a_len, int* b, int& b_len) { for (int i=0; i < a_len; ++i) b[i] = a[i]; b_len = a_len; } //高精度加法计算函数 void add(int a[], int a_len, int b[], int b_len, int* result, int& result_len) { int i = 0, x = 0; result_len = 0; int max_a_b = a_len > b_len ? a_len : b_len; while (i< max_a_b) { int sub_a = i < a_len ? a[i] : 0; int sub_b = i < b_len ? b[i] : 0; result[i] = sub_a + sub_b + x; x = result[i] / 10; result[i] %= 10; result_len++; i++; } if (x != 0) { result[i] = x; ++result_len; } } //高精度减法计算函数 void jianfa(int a[], int a_len, int b[], int b_len, int* result, int& result_len) { int i = 0, x = 0; result_len = 0; int max_a_b = a_len > b_len ? a_len : b_len; while (i < max_a_b) { int sub_a = i < a_len ? a[i] : 0; int sub_b = i < b_len ? b[i] : 0; // 减法 if (sub_a < sub_b) { --a[i + 1]; sub_a += 10; } result[i] = sub_a - sub_b; result_len++; i++; } if (x != 0) { result[i] = x; ++result_len; } // 假如结果全都是0,输出一个0 int sum(0); for (int i = 0; i < result_len; ++i) { sum += result[i]; if (sum != 0) break; } if (sum == 0) result_len = 1; } // 如果 a==b 返回 0; 如果 a > b, 返回 1; 如果 a < b, 返回 -1; int compare_a_b(int a[], int a_len, int b[], int b_len) { if (a_len > b_len) return 1; else if (a_len < b_len) return -1; else { for (int i= a_len-1; i>=0; --i) // 加减的结果,是倒过来的,所以这里的比较也要倒着比较 { if (a[i] > b[i]) return 1; else if (a[i] < b[i]) return -1; else continue; } } return 0; // 相等 } void divide(int a[], int a_len, int b[], int b_len, int* shang, int& shang_len, int* yushu, int& yushu_len) { if (0 == compare_a_b(zero, 1, a, a_len)) { shang_len = 1; yushu_len = 1; return; // 0是被除数,则商为0 } for (;;) { jianfa(a, a_len, b, b_len, a, a_len); // 相当于 a -= b; add(shang, shang_len, one, 1, shang, shang_len); assign(a, a_len, yushu, yushu_len);// 将最终减法剩余的结果,赋值给余数 if (-1 == compare_a_b(a, a_len, b, b_len)) // break; // 当余数a小于除数b的时候,退出循环。 } } int main() { // 从屏幕获得输入 string astr, bstr; cin >> astr; cin >> bstr; int lena = astr.size(), lenb = bstr.size(); int a[100] = { 0 }, b[100] = { 0 }; for (int i = 0; i< lena; ++i) a[lena - i - 1] = astr[i] - '0'; // 将数字倒过来 for (int i = 0; i< lenb; ++i) b[lenb - i - 1] = bstr[i] - '0'; if (0 == compare_a_b(zero, 1, b, lenb)) return -1; // 被除数不能是 0 // 计算除法 int shang[100] = { 0 }, yushu[100] = { 0 }, shang_len(0), yushu_len(0); divide(a, lena, b, lenb, shang, shang_len, yushu, yushu_len); // a 是被除数,是高精度值。b是低精度值,用 int 即可存储 // 输出 cout << "商:"; for (int i=0; i < shang_len; ++i) cout << shang[shang_len - i - 1]; // 再将数字倒过来输出 cout << endl; cout << "余数:"; for (int i = 0; i < yushu_len; ++i) cout << yushu[yushu_len - i - 1]; // 再将数字倒过来输出 cout << endl; return 0; }