高精度对于有些语言来说都有成型的库函数供程序员使用,但是其中对于算法的考量来说,我们还是希望重新感受一下实现的过程。
以c++为例,对于一般的 int,long,或者 long long 类型的能够满足大多数场景,但对于成百上千位的大数来说这些就无能为力了。
本文参考相关资料整理了加减乘除的大数算法小题目。 回顾一下最基本的运算规则用程序如何实现。
大数加法给定两个正整数,不含前导0, 计算他们的和。
整数长度范围 [1, 100000]
实现思路也比较直接,我们无非是要按位相加,时刻记住进位补数即可,根本在于我们用一个数组的结构来存储数的每一位。
#include#include #include using namespace std; int main() { string a, b; cin >> a >> b; reverse(a.begin(), a.end()); reverse(b.begin(), b.end()); //string res = ""; vector res; int t = 0; for (size_t i = 0, j = 0; i < a.size() || j < b.size(); i++, j++) { if (i < a.size()) t += a[i] - '0'; if (j < b.size()) t += b[j] - '0'; //res = to_string(t % 10) + res; res.push_back(t % 10); t = t / 10; } if (t > 0) res.push_back(t); for (int i = res.size() - 1; i >= 0; i--) cout << res[i]; //if(t > 0) res = to_string(t) + res; //cout << res; return 0; }
从注释中可以看出我原来用的是字符串拼接和存储中间结果,实际的效果比当前时间耗时高一个数量级,因此是不可取的,相信可以猜到字符串拼接存在大量的空间重分配和拷贝操作,具体原因可能到细究一下 c++ 的string 实现的考量,这个问题其实不简单,希望有机会能写写。
大数减法给定两个正整数(不含前导 0),计算它们的差,计算结果可能为负数。
与加法不同的是,减法考虑的是从高位借位。尤其最后可能会出现前导0,要处理一下。
#include大数乘法(大数乘小数)#include #include using namespace std; bool cmp1(string a, string b) { if (a.size() != b.size()) return a.size() < b.size(); return a < b; } vector sub(string a, string b) { vector res; int borrow = 0; for (int i = 0, j = 0; i < a.size() || j < b.size(); i++, j++) { int t = a[i] - '0' - borrow; if (j < b.size()) t = t - (b[j] - '0'); if (t < 0) { borrow = 1; t = t + 10; } else borrow = 0; res.push_back(t); } return res; } int main() { string a, b; cin >> a >> b; bool sw = 0; if (cmp1(a, b)){ swap(a, b); sw = true; } reverse(a.begin(), a.end()); reverse(b.begin(), b.end()); vector res = sub(a, b); if (sw) cout << "-"; // 处理前导0 int i = res.size() - 1; while(res[i] == 0 && i > 0)i--; while(i >= 0) cout << res[i--]; return 0; }
给定两个非负整数(不含前导 0) A 和 B,请你计算 A x B 的值
A 的长度 [1, 100000]
B 的大小 【0, 10000】
思路是,把小数B乘A的每一位,然后加起来,因为每次数位高一位,所以错位加,从A的低位到高位,每次加起来。
可以确定的是每次的乘法运算都是小数,每次有且仅有一结果位可以确认。
#include大数除法(大数除小数)#include using namespace std; int main() { string a; int b; cin >> a >> b; int lastSeg = 0; vector res; reverse(a.begin(), a.end()); for (int i = 0; i < a.size(); i++) { int t = b * (a[i] - '0') + lastSeg; res.push_back(t % 10); lastSeg = t / 10; } while(lastSeg > 0) { res.push_back(lastSeg%10); lastSeg /= 10; } int i = res.size() - 1;while(res[i] == 0 && i > 0) i--; while (i >= 0) cout << res[i--]; return 0; }
除法采用试除法,每次从高位开始,除以小数,可以除就得一位商,不可补0,剩余增加一位后继续以此类推。最后不能除的是余数。
最后记得处理前导0.
#include不足#include using namespace std; int main() { string a; int b; cin >> a >> b; // 试除法 vector dv; int md = 0; int obj = 0; for (int i = 0; i < a.size(); i++) { obj = obj * 10 + a[i] - '0'; if (obj < b) { dv.push_back(0); continue; } dv.push_back(obj/b); obj = obj % b; } md = obj; int i = 0; while(dv[i] == 0 && i < dv.size() - 1) i++; while(i < dv.size()) cout << dv[i++]; cout << endl; cout << md; return 0; }
当然考察一个成熟的工业级实现是另一个话题了,拿string来说,不同的程序组件实现都会有很多细节的考量,例如 redis的SDS,mysql的字符串实现。工程实践使用还是要多翻一下源代码,以便写出更加健壮优雅的程序实现。
参考文献
[1] https://www.acwing.com/
[2]《c++ 标准程序库》Nicolai M.Josuttis , 候捷,孟岩 译



