高精度加减
一、高精度运算核心前提:理解“数组模拟数位”
在开始具体运算前,我们首先要解决一个关键问题:如何存储超大数?答案很简单——用数组(或容器)拆分存储每一位数字,这里有一个黄金存储规则:低位在前,高位在后。
举个例子,数字1234,常规思维是按“高位到低位”存储为[1,2,3,4],但在高精度运算中,我们会存储为[4,3,2,1](数组下标0对应个位,下标1对应十位,下标2对应百位,下标3对应千位)。
为什么要这样存储?原因很简单:加减运算都是从最低位(个位)开始的,低位在前的存储方式,能让我们从数组下标0开始逐位运算,进位、借位时只需操作相邻下标,无需移动整个数组,代码逻辑更简洁,运算效率也更高。后续输出结果时,只需逆序遍历数组即可得到正常的数字顺序。
另外,由于我们通常用字符串输入超大数(直接输入数字会溢出),所以需要先将字符串转换为数组,转换时注意:字符串的第一个字符是数字的最高位,需要逆序存入数组,同时通过字符减去'0'的方式,将字符转换为对应的数字。
二,高精度加法:逐位相加,处理进位
2.1 核心原理(复刻竖式加法)
我们小学学的竖式加法,就是高精度加法的核心逻辑,以1234 + 5678为例:
1234 + 5678 ------ 6912
拆解下来,核心步骤只有3步:
对齐:将两个数字的最低位对齐(对应数组下标0的位置);
逐位相加:从最低位(数组下标0)开始,将两个数对应位的数字相加,再加上前一位的进位(初始进位为0);
处理进位:如果当前位的和≥10,那么进位设为和÷10,当前位保留和%10;如果所有位都处理完后仍有进位,需将进位作为新的最高位存入结果数组。
适用场景:超大数求和、斐波那契递推、整数划分DP转移等场景,都是高精度加法的常见应用。
2.2 完整代码实现(C++版,入门友好)
这里用vector容器存储数组(无需手动管理数组长度,更便捷),实现两个非负超大数的加法,代码包含详细注释,新手可直接复制运行:
#include <iostream> #include <vector> #include <algorithm> // 用于reverse函数 using namespace std; // 高精度加法:a + b,返回结果(字符串形式) string add(string a, string b) { // 步骤1:将字符串转换为vector数组(低位在前) vector<int> numA, numB, res; for (int i = a.size() - 1; i >= 0; --i) { numA.push_back(a[i] - '0'); // 字符转数字,逆序存入 } for (int i = b.size() - 1; i >= 0; --i) { numB.push_back(b[i] - '0'); } // 步骤2:逐位相加,处理进位 int carry = 0; // 进位标记,初始为0 int i = 0; // 遍历两个数组,直到所有位都处理完,且进位为0 while (i < numA.size() || i < numB.size() || carry != 0) { // 取出当前位的数字,若数组已遍历完,补0 int x = i < numA.size() ? numA[i] : 0; int y = i < numB.size() ? numB[i] : 0; // 计算当前位的和 = 甲数字当前位 + 乙数字当前位 + 进位 int sum = x + y + carry; res.push_back(sum % 10); // 保留当前位(个位) carry = sum / 10; // 更新进位 i++; } // 步骤3:将结果数组逆序,转换为字符串 string result; for (int i = res.size() - 1; i >= 0; --i) { result += (res[i] + '0'); // 数字转字符 } return result; } int main() { // 测试案例 string a, b; cout << "请输入第一个非负超大数:"; cin >> a; cout << "请输入第二个非负超大数:"; cin >> b; string sum = add(a, b); cout << "两数之和:" << sum << endl; return 0; }2.3 关键注意点(避坑必看)
适用场景:超大数求差、大数比较、区间范围计算等场景。
3.2 完整代码实现(C++版,兼容正负)
代码包含“大小比较”“借位处理”“前导零去除”三个核心模块,注释详细,可直接复用:
进位处理:一定要记得处理“最高位进位”,比如999 + 1 = 1000,若不处理进位,结果会是000,遗漏最高位的1;
数组补0:当两个数字位数不同时,短的数组后续位补0,避免数组越界;
输入输出:始终用字符串接收输入、输出结果,避免直接用数字类型导致溢出。
三、高精度减法:逐位相减,处理借位
3.1 核心原理(复刻竖式减法)
减法比加法多了两个步骤:比较大小和处理借位,以5231 - 1789为例:
5231 - 1789 ------ 3442
核心步骤拆解:
比较大小:先判断被减数a是否大于等于减数b。如果a < b,结果为负数,此时需要交换a和b,计算b - a,最后在结果前加负号;
对齐:同样将两个数字的最低位对齐(数组下标0);
逐位相减:从最低位开始,用a的当前位减去b的当前位,再减去前一位的借位(初始借位为0);
处理借位:如果当前位相减后小于0,说明需要向高位借位,此时当前位加10,借位设为1;若相减后大于等于0,借位设为0;
去除前导零:运算完成后,结果数组可能存在前导零(比如1000 - 999 = 0001),需要去除,只保留有效位数(注意:结果为0时,需保留一个0)。
#include <iostream> #include <vector> #include <algorithm> using namespace std; // 辅助函数:比较两个非负数字符串的大小,返回true表示a >= b bool compare(string a, string b) { // 先比较长度,长度长的数字更大 if (a.size() != b.size()) { return a.size() > b.size(); } // 长度相同,逐位比较(从高位到低位) for (int i = 0; i < a.size(); ++i) { if (a[i] != b[i]) { return a[i] > b[i]; } } return true; // 两数相等 } // 高精度减法:a - b(保证a >= b,返回非负结果字符串) string subtractCore(string a, string b) { // 步骤1:字符串转数组(低位在前) vector<int> numA, numB, res; for (int i = a.size() - 1; i >= 0; --i) { numA.push_back(a[i] - '0'); } for (int i = b.size() - 1; i >= 0; --i) { numB.push_back(b[i] - '0'); } // 步骤2:逐位相减,处理借位 int borrow = 0; // 借位标记,初始为0 int i = 0; while (i < numA.size()) { // 取出当前位数字,b遍历完则补0 int x = numA[i]; int y = i < numB.size() ? numB[i] : 0; // 计算当前位差值 = 甲当前位 - 乙当前位 - 借位 int diff = x - y - borrow; if (diff < 0) { // 需要借位 diff += 10; borrow = 1; } else { // 无需借位 borrow = 0; } res.push_back(diff); i++; } // 步骤3:去除前导零(保留至少一个0) while (res.size() > 1 && res.back() == 0) { res.pop_back(); } // 步骤4:数组逆序转字符串 string result; for (int i = res.size() - 1; i >= 0; --i) { result += (res[i] + '0'); } return result; } // 高精度减法主函数:处理正负情况 string subtract(string a, string b) { if (compare(a, b)) { // a >= b,直接计算a - b return subtractCore(a, b); } else { // a< b,结果为负,计算b - a后加负号 return "-" + subtractCore(b, a); } } int main() { // 测试案例 string a, b; cout << "请输入被减数(超大数):"; cin >> a; cout << "请输入减数(超大数):"; cin >> b; string diff = subtract(a, b); cout << "两数之差:" << diff << endl; return 0; }3.3 关键注意点(避坑必看)
大小比较:必须先判断被减数和减数的大小,否则会出现负数结果遗漏负号的问题;
借位处理:借位后,高位数字会减少1,后续计算时必须考虑这个借位,避免漏算;
前导零:去除前导零时,要保证结果至少保留一个0(比如0 - 0 = 0,不能返回空字符串);
正负处理:只有当a < b时,结果才加负号,其他情况直接返回非负结果。
