高精度加法
在C/C++中,我们经常会碰到限定数据范围的情况,我们先来看看常用的int和long long两种数据类型的范围吧。
C++标准规定:int占一个机器字长。在32位系统中int占32位,即4个字节,所以int的范围是[-2的31次方,2的31次方-1],为1e8数量级;longlong的范围则是[-2的63次方,2的63次方-1],为1e18数量级。如果超过该数量级,该怎么办?
此时就需要用到高精度算法
输入两个整数a,b输出他们的和(a,b<=10的500次方)
这时我们只能使用高精度来解决
高精度加法的核心思想:
1.将大数分解成每一位的数字: 将大数按位分解为数组或字符串形式,使得可以逐位进行加法操作。通常情况下,数字的每一位都会以逆序存储(即个位在前),方便进行加法。
2.逐位加法: 从最低位开始进行加法。对于每一位,计算对应的数字之和,并处理进位。进位可能会影响到下一位的计算,因此需要将进位信息传递到下一位。
3.处理进位: 每一位的计算可能会产生进位,需要将进位加到下一位的计算中。
4.去除前导零: 计算完所有位后,结果可能会有前导零,需要去除这些零以得到最终的结果。
高精度加法的步骤:
1.初始化:
创建足够大的数组来存储每一位的结果,通常比输入数字的长度多一个位置以处理进位。
2.数字转换:
将输入的大数(字符串形式)转换为数字形式,并存储在数组中。由于我们处理的数字是逆序存储的,个位在数组的第一个位置。
3.执行加法:
从最低位(数组的第一个位置)开始,对每一位进行加法操作。计算每一位的和,并存储在结果数组中,同时处理进位。
4.处理进位:
每次加法操作后,计算进位并将其加到下一位的计算中。
5.去除前导零:
检查结果数组中是否有前导零,并将其去除。
6.输出结果:
从结果数组中输出高精度加法的结果,通常是从数组的最后一个有效位置(最高位)开始输出。
我们来看一道题这道题就运用了高精度加法
代码如下:
#include <iostream>
#include <cstring> // 用于 strlen 函数
#include <algorithm> // 用于 max 函数
using namespace std;
char s1[505], s2[505]; // 存储输入的大数
int a[505], b[505], c[505]; // 存储数字形式的输入和结果
int main()
{
int la, lb, lc; // s1、s2 和结果数组的长度
// 读取两个大数作为字符串
cin >> s1;
cin >> s2;
// 获取两个输入字符串的长度
la = strlen(s1);
lb = strlen(s2);
// 将 s1 的字符转换为整数,并以逆序存储到数组 a 中
for (int i = 0; i < la; i++)
{
a[la - i] = s1[i] - '0'; // 通过减去 '0' 将字符转换为整数
}
// 将 s2 的字符转换为整数,并以逆序存储到数组 b 中
for (int i = 0; i < lb; i++)
{
b[lb - i] = s2[i] - '0'; // 通过减去 '0' 将字符转换为整数
}
// 确定结果数组的最大长度,加一是为了处理可能的进位
lc = max(la, lb) + 1;
// 逐位进行加法运算
for (int i = 1; i <= lc; i++)
{
c[i] += a[i] + b[i]; // 将对应位的数字和进位相加
c[i + 1] = c[i] / 10; // 计算进位
c[i] = c[i] % 10; // 当前位的结果
}
// 移除结果数组中的前导零
if (c[lc] == 0 && lc > 0)
{
lc--;
}
// 从最高位到最低位打印结果
for (int i = lc; i > 0; i--)
{
cout << c[i]; // 打印结果的每一位
}
return 0;
}
下面是用y总模板写的
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 使用向量实现高精度加法
vector<int> add(vector<int> &A, vector<int> &B)
{
// 确保 A 总是比 B 长(长度可能不同)
if (A.size() < B.size()) return add(B, A);
vector<int> C; // 存储结果的向量
int carry = 0; // 存储当前位的进位
// 遍历 A 中的每一位
for (int i = 0; i < A.size(); i++)
{
carry += A[i]; // 将 A 的当前位加到 carry 中
if (i < B.size()) carry += B[i]; // 如果 B 还有相应位,加上 B 的当前位
C.push_back(carry % 10); // 当前位的结果是 carry 除以 10 的余数
carry /= 10; // 更新进位 carry,即 carry 除以 10 的商
}
// 如果最后还有进位 carry,则将其添加到结果中
if (carry) C.push_back(carry);
return C; // 返回结果
}
int main()
{
string s1, s2;
cin >> s1 >> s2;
// 将输入字符串转换为向量(每个字符转换为整数,且逆序存储)
vector<int> A(s1.size()), B(s2.size());
for (int i = 0; i < s1.size(); i++) {
A[s1.size() - 1 - i] = s1[i] - '0';
}
for (int i = 0; i < s2.size(); i++) {
B[s2.size() - 1 - i] = s2[i] - '0';
}
// 调用加法函数得到结果
vector<int> C = add(A, B);
// 去除前导零并输出结果
int start = C.size() - 1;
while (start > 0 && C[start] == 0) {
start--;
}
for (int i = start; i >= 0; i--) {
cout << C[i];
}
cout << endl;
return 0;
}
高精度加法模板
模板来自于y总
#include <vector>
using namespace std;
// C = A + B, A >= 0, B >= 0
vector<int> add(vector<int> &A, vector<int> &B)
{
// 确保 A 总是比 B 长(长度可能不同)
if (A.size() < B.size()) return add(B, A);
vector<int> C; // 存储结果的数组
int t = 0; // 存储当前位的和以及进位
// 遍历 A 中的每一位
for (int i = 0; i < A.size(); i ++ )
{
t += A[i]; // 将 A 的当前位加到 t 中
if (i < B.size()) t += B[i]; // 如果 B 还有相应位,加上 B 的当前位
C.push_back(t % 10); // 当前位的结果是 t 除以 10 的余数
t /= 10; // 更新进位 t,即 t 除以 10 的商
}
// 如果最后还有进位 t,则将其添加到结果中
if (t) C.push_back(t);
return C; // 返回结果
}
高精度减法
基本步骤
1. 数的表示方式
由于普通数据类型不能表示超大整数,高精度减法通常将两个大数以字符串或数组的形式存储。每一位数字分别存储到字符串或数组的元素中,从而避免溢出的问题。
2. 确定符号与大小
在进行减法之前,首先要确定两个大数的大小,判断被减数和减数的相对大小,以确保不会出现负数。
如果被减数小于减数,那么结果为负数,符号应在结果前加上负号。
如果被减数大于或等于减数,则可以直接进行相减操作。
3. 位对位相减
高精度减法的核心是模仿我们平常手算的逐位相减:
从最低位(即末尾)开始,依次对每一位进行减法运算。
如果当前位的被减数小于减数,需要向高位借位,将高位的值减1,当前位的值加上10再进行减法。
重复这个过程,直到所有位都减完。
4. 处理借位
当某一位的减数大于被减数时,就需要从高位借位。这一步骤的难点在于需要处理连续的借位情况:
若上一位需要借位,则本位需减1,并继续判断是否需要继续借位。
5. 去除前导零
计算完所有位后,可能会出现前导零(如1000 - 999 = 0001)。此时需要去掉这些前导零,确保结果的格式正确。
我们来看一道题这道题就运用了高精度减法
代码如下:
#include<iostream>
#include<cstring> // 用于strlen, strcpy等字符串处理函数
using namespace std;
char s1[10090], s2[10090], s3[10090]; // 字符数组用于存储输入的两个字符串以及临时交换用的字符串
int a[10090], b[10090], c[10090]; // 数组a, b分别存储字符串s1和s2的数字表示,c存储结果
int flag = 0; // 标志变量,用于判断是否需要输出负号
// 比较两个字符串表示的数值大小,如果s1 >= s2返回true,否则返回false
bool compare(char s1[], char s2[])
{
int u = strlen(s1), v = strlen(s2); // 获取s1和s2的长度
if (u != v) // 如果长度不同,则长度大的数更大
{
return u > v;
}
for (int i = 0; i < u; i++) // 长度相同的情况下,逐位比较
{
if (s1[i] != s2[i]) // 如果某一位不同,则返回该位较大的结果
{
return s1[i] > s2[i];
}
}
return true; // 如果所有位都相同,s1 == s2,返回true
}
int main()
{
int la, lb, lc; // 定义变量存储s1、s2的长度以及最终结果的位数
cin >> s1 >> s2; // 输入两个字符串表示的大数
// 如果s1 < s2,交换s1和s2,使得大数在前
if (!compare(s1, s2))
{
flag = 1; // 标志置为1,表示需要输出负号
strcpy(s3, s1); // 用临时数组s3保存s1的值
strcpy(s1, s2); // 将s2赋值给s1
strcpy(s2, s3); // 将临时数组s3的值赋给s2,完成交换
}
la = strlen(s1); // 获取s1的长度
lb = strlen(s2); // 获取s2的长度
// 将s1和s2的字符转为数字并倒序存入a和b数组
for (int i = 0; i < la; i++)
{
a[la - i] = s1[i] - '0'; // 将s1的第i个字符转为数字,倒序存入a
}
for (int i = 0; i < lb; i++)
{
b[lb - i] = s2[i] - '0'; // 将s2的第i个字符转为数字,倒序存入b
}
lc = max(la, lb); // lc是结果的最大可能位数,即la和lb中的最大值
// 从低位到高位进行逐位减法
for (int i = 1; i <= lc; i++)
{
if (a[i] < b[i]) // 如果当前位a[i]小于b[i],需要向高位借位
{
a[i + 1]--; // 向高位借1
a[i] += 10; // 当前位加10
}
c[i] = a[i] - b[i]; // 计算当前位的差值并存入c数组
}
// 去掉前导零
while (c[lc] == 0 && lc > 1)
{
lc--; // 如果最高位是0,且lc > 1,则将lc减1
}
// 如果flag标志为1,说明s1 < s2,需要输出负号
if (flag == 1)
{
cout << '-';
}
// 输出结果,从最高位输出到最低位
for (int i = lc; i > 0; i--)
{
cout << c[i];
}
return 0;
}
下面代码用y总模板写的
#include<iostream>
#include<vector>
#include<string>
using namespace std;
// 大数减法函数,计算 C = A - B,前提条件是 A >= B
vector<int> sub(vector<int> &A, vector<int> &B)
{
vector<int> C; // 定义一个vector C,用于存储结果
for (int i = 0, t = 0; i < A.size(); i++) // 从A的最低位到最高位遍历,t表示借位
{
t = A[i] - t; // 当前位A[i]减去上一次的借位t
if (i < B.size()) t -= B[i]; // 如果当前位i在B范围内,则从t中再减去B[i]
// 处理当前位的差值,并将结果存入C
// (t + 10) % 10 确保结果在0到9之间,处理借位情况
C.push_back((t + 10) % 10);
// 如果t小于0,说明借位发生,设置t为1表示向下一位借位
// 否则t为0,不需要借位
if (t < 0) t = 1;
else t = 0;
}
// 去掉结果C中的前导零,如果C的长度大于1,且最后一位是0,则将其删除
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C; // 返回存储结果的vector C
}
// 比较两个大数 A 和 B,如果 A >= B 返回 true,否则返回 false
bool cmp(const vector<int>& A, const vector<int>& B)
{
if (A.size() != B.size()) return A.size() > B.size(); // 位数不同则直接比较位数
for (int i = A.size() - 1; i >= 0; i--) // 从高位到低位逐位比较
{
if (A[i] != B[i]) return A[i] > B[i];
}
return true; // 如果每一位都相同,则 A == B
}
int main()
{
string s1, s2;
cin >> s1 >> s2;
// 将字符串 s1 和 s2 转换为数字存储在 A 和 B 中,倒序存储以便逐位计算
vector<int> A(s1.size()), B(s2.size());
for (int i = 0; i < s1.size(); i++)
{
A[s1.size() - 1 - i] = s1[i] - '0'; // 倒序存储 s1 中的数字
}
for (int i = 0; i < s2.size(); i++)
{
B[s2.size() - 1 - i] = s2[i] - '0'; // 倒序存储 s2 中的数字
}
bool isNegative = false; // 记录最终结果是否为负数
if (!cmp(A, B)) // 如果 A < B,则交换 A 和 B,确保 A >= B
{
swap(A, B);
isNegative = true; // 结果是负数
}
// 调用大数减法函数
vector<int> C = sub(A, B);
// 输出结果,去掉前导零
if (isNegative) cout << '-'; // 如果结果是负数,输出负号
for (int i = C.size() - 1; i >= 0; i--)
{
cout << C[i];
}
cout << endl;
return 0;
}
高精度减法模板
// 计算C = A - B,其中A >= B,且A和B都是非负整数
vector<int> sub(vector<int> &A, vector<int> &B)
{
vector<int> C; // 定义一个vector C,用于存储结果
for (int i = 0, t = 0; i < A.size(); i++) // 从A的最低位到最高位遍历,t表示借位
{
t = A[i] - t; // 当前位A[i]减去上一次的借位t
if (i < B.size()) t -= B[i]; // 如果当前位i在B范围内,则从t中再减去B[i]
// 处理当前位的差值,并将结果存入C
// (t + 10) % 10 确保结果在0到9之间,处理借位情况
C.push_back((t + 10) % 10);
// 如果t小于0,说明借位发生,设置t为1表示向下一位借位
// 否则t为0,不需要借位
if (t < 0) t = 1;
else t = 0;
}
// 去掉结果C中的前导零,如果C的长度大于1,且最后一位是0,则将其删除
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C; // 返回存储结果的vector C
}
gitee源码