Files
T

122 lines
5.6 KiB
Markdown

# **C++ 高精度加法**
#### **一:为什么需要高精度计算?**
在编程中,我们常用的 `int`、`long long` 等数据类型都有其表示范围的上限。例如,一个 64 位的 `long long` 最大也只能存储约 `9 x 10^18` 的数字。当我们面临的问题需要处理的数值远超这个范围时——比如计算 100 的阶乘(一个有 158 位的天文数字),或者在密码学中处理超长大整数——标准数据类型便无能为力了。
**高精度计算**,就是为了解决这个问题而生。它利用字符串或数组来模拟我们人类进行笔算的过程,从而处理任意大(只要内存允许)的整数运算。而接下来就会讲解最基础也最重要的运算:**高精度加法**。
#### **二:算法思路**
**1. 方法一:双指针法**
最自然的思路是**模拟小学的竖式计算**:末位对齐,从右到左逐位相加,并处理进位。如果用代码直接实现,通常是这样的:
* 使用两个指针 `i` 和 `j`,分别指向两个字符串 `a` 和 `b` 的末尾。
* 在一个 `while (i >= 0 || j >= 0)` 的循环中进行计算。
* 循环内部需要写复杂的 `if-else` 来处理:`i` 和 `j` 都有效、只有 `i` 有效、只有 `j` 有效这三种情况。
这种方法虽然直观,但**代码实现相对复杂,边界条件多,容易出错**。
**2. 反转对齐法**
为了规避双指针的复杂性,我们引入一种实现更简洁的思路:
**“与其费力地从后往前处理复杂的边界,不如我们先把它变成一个简单的、从头走到尾的问题!”**
这就是“反转对齐法”的核心思想。它通过预处理,将所有情况(如两个数长度不同)统一为一种标准模型,使得最核心的“逐位相加”过程可以用一个极其简单的单循环完成。
#### **三:核心流程**
让我们通过计算 `987 + 56` 来感受这个这个过程:
**原始数字**
* `a = "987"`
* `b = "56"`
**第一步:全部反转**
我们将两个字符串都进行反转,这样它们的个位、十位、百位...就分别对齐到了字符串的索引 `0, 1, 2...`。
* `a` 变为 `"789"`
* `b` 变为 `"65"`
**第二步:补零对齐
为了让两个字符串的每一位都能一一对应相加,我们将较短的字符串在**末尾补 `0`,直到它们等长。
* `a` 保持 `"789"`
* `b` 变为 `"650"`
> 在反转后的字符串**末尾**补 `0`,等价于在原始数字的**开头**补 `0` (例如,`56` 变成 `056`),这是在模拟竖式计算的末位对齐。
**第三步:从左到右逐位相加 **
由于已经反转对齐,我们现在可以从索引 `0` 开始,用一个简单的 `for` 循环来完成所有位的相加。
```
(carry) 0 -> 1 -> 1 -> 1 (进位变化过程)
7 8 9 (反转后的 a)
+ 6 5 0 (反转后的 b)
-----------------
(ans) 3 -> 4 -> 0 (倒序的结果 ans)
```
* **i = 0 (个位):** `carry` 是 `0`。`7 + 6 + 0 = 13`。结果存 `3`,新 `carry` 为 `1`。`ans = "3"`。
* **i = 1 (十位):** `carry` 是 `1`。`8 + 5 + 1 = 14`。结果存 `4`,新 `carry` 为 `1`。`ans = "34"`。
* **i = 2 (百位):** `carry` 是 `1`。`9 + 0 + 1 = 10`。结果存 `0`,新 `carry` 为 `1`。`ans = "340"`。
**第四步:处理最终进位**
循环结束后,检查是否还有剩余的进位。在这里,`carry` 等于 `1`,需要追加到结果的末尾。
* `ans` 变为 `"3401"`。
**第五步:再次反转**
我们得到的 `ans` 是最终结果的倒序。现在,我们把它反转回来,得到正确的答案。
* `"3401"` 反转后得到 `"1043"`。
至此,计算完成!这个过程将一个复杂的、需要处理多种情况的双指针问题,转化成了一个简单的、逻辑统一的单循环问题。
#### **四:代码实现**
```cpp
string add(string a, string b) {
// 将两个输入字符串都反转
// 目的:让个位在 [0] 位置,方便从左到右处理
reverse(a.begin(), a.end());
reverse(b.begin(), a.end());
// 在较短的字符串末尾补 0,使其与较长的等长
// 目的:统一循环次数,避免复杂的边界判断
while (a.size() < b.size()) a += '0';
while (b.size() < a.size()) b += '0';
string ans = ""; // 存放倒序的结果
int carry = 0; // 初始化进位
// 从左到右逐位相加 (i=0 是个位,i=1 是十位...)
for (int i = 0; i < a.size(); i++) {
// 计算当前第 i 位的总和
int sum = (a[i] - '0') + (b[i] - '0') + carry;
// 将结果的个位数(字符形式)拼接到 ans 的末尾
ans += to_string(sum % 10);
// 更新进位,为下一轮做准备
carry = sum / 10;
}
// 如果循环结束后仍有进位 (如 99+1=100), 则追加到 ans 末尾
if (carry > 0) {
ans += to_string(carry);
}
// 将倒序的结果 ans 再次反转,得到最终答案
reverse(ans.begin(), ans.end());
return ans;
}
```
#### **第五部分:总结与要点**
* **核心思路**:通过 `反转 -> 补零 -> 相加 -> 再反转` 的流程,将问题简化。
* **设计思想**:**将复杂性从核心循环中剥离,移入预处理和后处理阶段**。
* 通过预处理将所有输入(无论长度如何)都统一为相同长度的字符串,避免了在核心循环中进行繁琐的长度判断,大大减少了出错的可能性。
* **技巧**:在**反转后**的字符串**末尾**补零,是实现**原始数字**在**开头**对齐的关键。