C++基础算法①——高精度乘除法计算 1. 高精度计算入门为什么需要它当你第一次听说高精度计算时可能会觉得这是个高大上的概念。其实它的核心思想非常简单用数组来模拟手算过程。想象一下小学时做的竖式计算只不过现在是用代码来实现这个过程。为什么普通数据类型不够用呢举个例子C中的long long最多只能存储约18位数字。但如果你要计算100位的阶乘比如50!或者处理两个1000位数字的乘法这时候常规数据类型就无能为力了。这时候就需要高精度算法来救场。我刚开始学高精度时也犯过迷糊——为什么要把数字倒着存后来发现这和数组下标从0开始的特性有关。比如数字12345个位5在字符串末尾但计算时我们希望a[0]对应个位所以需要倒序存储为[5,4,3,2,1]。2. 高精度乘法实现2.1 基础乘法原理高精度乘法的核心就是模拟竖式计算。还记得小学时怎么算123×45吗先算123×5再算123×4记得左移一位最后相加。代码实现也是这个思路vectorint multiply(vectorint a, vectorint b) { vectorint res(a.size()b.size(), 0); for(int i0; ia.size(); i){ for(int j0; jb.size(); j){ res[ij] a[i] * b[j]; // 关键步骤乘积累加 res[ij1] res[ij]/10; // 处理进位 res[ij] % 10; } } // 去除前导零 while(res.size()1 res.back()0) res.pop_back(); return res; }这个实现有几个关键点结果数组长度预设为a.size()b.size()两数乘积位数不会超过这个值双重循环实现逐位相乘实时处理进位避免数值溢出2.2 压位优化技巧基础实现虽然直观但效率不高。我在实际项目中发现采用压位存储可以大幅提升性能。所谓压位就是每个数组元素存储多位数字比如4位而不是1位vectorint multiply_optimized(vectorint a, vectorint b) { const int BASE 10000; // 每个元素存4位数 vectorint res(a.size()b.size(), 0); for(int i0; ia.size(); i){ for(int j0; jb.size(); j){ res[ij] a[i] * b[j]; if(res[ij] BASE){ // 进位判断 res[ij1] res[ij]/BASE; res[ij] % BASE; } } } // 处理剩余进位 for(int i0; ires.size()-1; i){ if(res[i] BASE){ res[i1] res[i]/BASE; res[i] % BASE; } } while(res.size()1 res.back()0) res.pop_back(); return res; }实测下来压位存储可以使运算速度提升3-5倍。不过要注意输出时需要补前导零printf(%04d, res[i]); // 保证每4位输出3. 高精度除法详解3.1 高精度除以低精度这是相对简单的情况除数可以用普通整型存储。算法流程如下从被除数最高位开始处理当前余数×10 当前位数字计算商和新的余数重复直到处理完所有位数vectorint divide(vectorint a, int b, int r) { vectorint c; r 0; // 余数 for(int ia.size()-1; i0; i--){ r r*10 a[i]; c.push_back(r / b); r % b; } reverse(c.begin(), c.end()); while(c.size()1 c.back()0) c.pop_back(); return c; }注意这里余数是通过引用返回的调用方式int remainder; auto result divide(num, divisor, remainder);3.2 高精度除以高精度这是最复杂的情况需要用到减法模拟。基本思路是通过不断减去除数的倍数来得到商。具体实现时有个技巧先估算商的位数再逐位确定值。bool greater_eq(vectorint a, vectorint b, int offset) { // 比较a[offset..]是否b if(a.size()-offset b.size()) return false; if(a.size()-offset b.size()) return true; for(int ib.size()-1; i0; i--){ if(a[offseti] b[i]) return true; if(a[offseti] b[i]) return false; } return true; } vectorint divide(vectorint a, vectorint b) { vectorint c(a.size()-b.size()1, 0); vectorint remain(a); for(int ic.size()-1; i0; i--){ while(greater_eq(remain, b, i)){ // 执行减法 for(int j0; jb.size(); j){ remain[ij] - b[j]; if(remain[ij] 0){ remain[ij1]--; remain[ij] 10; } } c[i]; } } while(c.size()1 c.back()0) c.pop_back(); return c; }这个实现中有几个关键点greater_eq函数用于安全比较避免越界通过偏移量实现左移效果内层循环处理借位减法4. 实战技巧与常见问题4.1 输入输出处理高精度数通常以字符串形式输入。这里有个易错点字符串转数组时要考虑前导零和非法字符vectorint str_to_num(const string s) { vectorint a; bool valid false; for(int is.length()-1; i0; i--){ if(isdigit(s[i])){ if(!valid s[i]0) continue; // 跳过前导零 valid true; a.push_back(s[i]-0); } } if(a.empty()) a.push_back(0); // 处理全零情况 return a; } string num_to_str(const vectorint a) { string s; for(int ia.size()-1; i0; i--){ s to_string(a[i]); } return s.empty() ? 0 : s; }4.2 性能优化建议预分配内存在知道大概结果长度时提前reserve空间避免频繁扩容减少拷贝使用引用传递大参数特殊值处理乘0、除1等特殊情况可以快速返回并行计算对于超大数乘法可以考虑分治策略Karatsuba算法4.3 调试技巧高精度算法最容易出错的就是进位和借位处理。我常用的调试方法打印中间过程void debug_print(const vectorint v) { for(int iv.size()-1; i0; i--) coutv[i]; coutendl; }单元测试用例9999 × 99991000000000 - 1123456789 / 987边界值测试乘0除1相等数相减5. 完整代码示例下面是一个完整的高精度计算类实现class BigInt { vectorint digits; bool negative false; void trim_zeros() { while(digits.size()1 digits.back()0) digits.pop_back(); if(digits.size()1 digits[0]0) negativefalse; } public: BigInt(const string s) { bool valid false; int start 0; if(s[0] -) { negativetrue; start1; } for(int is.length()-1; istart; i--){ if(isdigit(s[i])){ if(!valid s[i]0) continue; valid true; digits.push_back(s[i]-0); } } if(digits.empty()) digits.push_back(0); } string to_string() const { string s; if(negative) s -; for(int idigits.size()-1; i0; i--) s std::to_string(digits[i]); return s; } BigInt operator*(const BigInt other) const { BigInt res(0); res.digits.resize(digits.size()other.digits.size(), 0); for(int i0; idigits.size(); i){ for(int j0; jother.digits.size(); j){ res.digits[ij] digits[i] * other.digits[j]; res.digits[ij1] res.digits[ij]/10; res.digits[ij] % 10; } } res.negative negative ^ other.negative; res.trim_zeros(); return res; } BigInt operator/(const BigInt other) const { if(other.digits.size()1 other.digits[0]0) throw runtime_error(Division by zero); BigInt quotient(0), remainder(*this); quotient.digits.resize(digits.size(), 0); for(int idigits.size()-other.digits.size(); i0; i--){ while(remainder.compare(other, i) 0){ // Subtract for(int j0; jother.digits.size(); j){ remainder.digits[ij] - other.digits[j]; if(remainder.digits[ij] 0){ remainder.digits[ij1]--; remainder.digits[ij] 10; } } quotient.digits[i]; } } quotient.negative negative ^ other.negative; quotient.trim_zeros(); return quotient; } int compare(const BigInt other, int offset0) const { // Similar to greater_eq but returns -1,0,1 if(digits.size()-offset other.digits.size()) return 1; if(digits.size()-offset other.digits.size()) return -1; for(int iother.digits.size()-1; i0; i--){ if(digits[offseti] other.digits[i]) return 1; if(digits[offseti] other.digits[i]) return -1; } return 0; } };使用示例BigInt a(123456789); BigInt b(987654321); auto c a * b; cout c.to_string(); // 输出乘积结果6. 算法选择与扩展当处理特别大的数字时比如超过10万位传统O(n²)的乘法效率就不够看了。这时候可以考虑更高级的算法Karatsuba算法将乘法分解为三个较小规模的乘法复杂度O(n^1.585)FFT乘法利用快速傅里叶变换复杂度O(n log n)不过这些优化算法实现起来复杂得多在一般的编程竞赛和日常应用中本文介绍的标准实现已经足够使用。高精度计算不仅是编程技巧的体现更是对计算机如何表示和处理数字的深入理解。我在实现银行利息计算系统时就曾因为没处理好高精度除法导致金额出现微小偏差。后来通过增加舍入规则和完善单元测试才解决了问题。