突破原生整数范围限制:C++高精度乘法算法模板的实现与优化
前引:在计算机科学中,整数运算的精度与范围是基础且关键的问题。C++标准数据类型(如int
、long long
)受限于固定位数,无法处理超出其表示范围的大整数乘法(例如超过18位的十进制数)。传统直接相乘方法会导致溢出,造成结果错误甚至程序崩溃。为解决这一问题,高精度算法通过模拟手工计算过程,结合数组或字符串的逐位操作,实现了任意精度的大整数乘法。本文将深入探讨一种基于C++的高效大数乘法实现方案,涵盖逆序存储优化、进位处理机制以及性能提升策略
目录
算法模板讲解
(1)翻转数字
(2)中间变量构建
(3)逐位相乘
(4)处理最高位多余的0
(5)逆置返回
注意事项
效果展示
完整代码
典型例题
小编寄语
算法模板讲解
此种方法是模拟人工手算进位,例如:45*5
它的计算过程是通过拆分计算:5*5 -> 4*5,中间考虑进位,最终得到225
下面我们来拆分讲解手工计算的过程,讲解代码原理,以534*4为例
当然在开始计算之前,如果有数字为0,那么可以直接返回,下面我们开始讲解算法!
(1)翻转数字
我们需要从最低位开始逐个计算乘积,但是不翻转不好控制下标,例如:534,翻转得到435
此时我们的计算应该是从左边开始:4*4->4*3->4*5
// 两个数反转,方便从低位到高位相乘
reverse(num1.begin(), num1.end());
reverse(num2.begin(), num2.end());
(2)中间变量构建
我们每次进位需要一个整型变量,我们假设为:carry
计算得到的结果需要一个string对象,我们假设为:string ans
// 两数相乘,其结果长度不会超过两数长度之和,先全部初始化为'0'string ans(num1.size() + num2.size(), '0');int carry = 0; // 进位
注意:两数相乘,位数不会超过两数长度之和,同时需要初始化为0,因为要在不同位放计算结果
(3)逐位相乘
这步是最关键的一步,下面我们分开讲解,例如:
(1)首先搭建两层 for 循环,要求分别可以表示每个数字的下标
for (int i = 0; i < num1.size(); ++i)
{for (int j = 0; j < num2.size(); ++j) {}}
(2)此时我们先用 carry 来保存拆开获得的乘积,同时该位可能还有之前的结果,所以还要加上
以方便后面获得进位 ,例如:
// 当前位的结果等于之前的进位加上两数当前位的乘积和之前计算的结果
carry += (num1[i] - '0') * (num2[j] - '0') + (ans[i + j] - '0');
(3)此时 carry 拿到了两数相乘的结果,但是我们应该只保存它的个位,例如:4*4=16 只存6
// 计算当前位的结果,保存到结果中
ans[i + j] = '0' + carry % 10;
注意:这里的 carry 的+0,就是“该位之前的计算结果”
(4)然后获得进位
// 更新进位
carry /= 10;
(5)一轮循环走完看是否有进位,有则需要将进位放在下一位,然后重置 carry 例如:
// 如果最后还有进位,将进位加到结果中
if (carry > 0) {ans[i + num2.size()] = ('0' + carry);carry = 0;
}
上面是第一轮循环,下面我们开始第二轮
(1)同样先处理两数相乘结果,记得加上该处可能原先存在的数字,例如:
// 当前位的结果等于之前的进位加上两数当前位的乘积和之前计算的结果
carry += (num1[i] - '0') * (num2[j] - '0') + (ans[i + j] - '0');
(2)存进结果在 ans 对应的下标中,例如:
// 计算当前位的结果,保存到结果中
ans[i + j] = '0' + carry % 10;
(3)获得进位,例如:
// 更新进位
carry /= 10;
(4)一轮循环走完,处理剩余的进位,把它存在当前下标的下一位,然后重置carry,例如:
// 如果最后还有进位,将进位加到结果中
if (carry > 0) {ans[i + num2.size()] = ('0' + carry);carry = 0;
}
这样我们前2轮循环就走完了,剩余的也只是根据循环重复
(4)处理最高位多余的0
防止在计算之后有多余的0,处理的现象例如:
// 如果最高位为0(即未被使用),则去掉最高位if (ans.back() == '0'){ans.pop_back();}
back:返回字符串最后一个字符的引用
pop_back: 移除最后最后一个字符,并缩短字符串长度
(5)逆置返回
上面我们已经获得了逆置计算的结果,再逆置回来就是正确结果了
// 将结果反转回正确的顺序
reverse(ans.begin(), ans.end());
// 返回结果
注意事项
(1)计算每两个数相乘的时候没有加上该位原先本来就存在的数
(2)单轮循环之后,在处理进位时,在存的时候下标不对,应该是外层下标+里层元素个数
(3)每次存在 ans 下标的只有个位,待一轮循环走完,结算剩余的进位
(4)为什么 整型数字 转 字母数字 需要+‘0’,反之-‘0’?
因为整型(0~9)加上字符‘0’的ASCII值(48)之后,会得到字符(‘0’~‘9’)的ASCII值
(5)我们正常来说是从右往左计算,但是现在翻转过来了,应该是从左往右
效果展示
完整代码
以下为完整算法,复制模板时只需要更改两个参数对象,以及返回值即可
string S1("534");
string S2("4");//先判断是否有乘0的情况
if (S1[0] - '0' == 0 || S2[0] - '0' == 0)
{//返回0
}
//逆置两个对象
reverse(S1.begin(), S1.end());
reverse(S2.begin(), S2.end());
//设置存储对象、进位变量
int carry = 0;
string tmp(S1.size() + S2.size(), '0');
//乘积计算
for (int i = 0; i < S1.size(); i++)
{for (int j = 0; j < S2.size(); j++){//carry获取乘积结果carry += (S1[i] - '0') * (S2[j] - '0') + (tmp[i + j] - '0');//存储乘积的个位tmp[i + j] = carry % 10 + '0';//获取进位carry /= 10;}//第一轮循环结束,结算剩余的进位if (carry > 0){//存储进位到当前的最高位tmp[i + S2.size()] = carry + '0';carry = 0;}
}
//整体逆置
reverse(tmp.begin(), tmp.end());
//去除最高位的0
if (tmp.back() == '0')
{tmp.pop_back();
}
//返回最终结果
典型例题
https://leetcode.cn/problems/multiply-strings
小编寄语
本文在处理整型计算乘积时,可以快速搭建算法模型框架,思路清晰,对于此类乘积题可以做到瞬秒的效果,比如:字符数字的乘积,整型数字的乘积,有了此算法,拿捏不是问题,轻松瞬秒!
【雾非雾】期待与你的下次相遇!