高精度运算
本文参考用C/C++实现基本高精度计算
一、为何要实现高精度
高精度的本质是有大数字参与的运算,由于有大数字的参与,在C/C++中,它超出数据类型例如long long等的表示范围,所以我们需要借用手动演算的方法来模拟运算。
二、准备阶段
首先,我们需要构造一个结构体来表示每一个大数字,用字符串表示数字的样子,用数组储存每一位上的数。
注: 数组需要倒序存储,即个位为a[0],十位为a[1]……
因为我们最初无法确定这个数到底有几位,为了方便运算,我们使用这样的存储方式。
构造完毕的结构体如下:
struct hp
{
int a[maxn];
char str[maxn];
int len;
void init() // 初始化
{
len = 0;
sign = true;
for(int i = 0; i < maxn; i++)
a[i] = str[i] = 0;
}
void Init() // 带输入的初始化方法
{
scanf("%s",str);
len = strlen(str);
sign = true;
for(int i = len - 1; i >= 0; i--)
a[len - i - 1] = str[i] - '0';
}
void Print()
{
printf("%s\n",str);
}
};
三、高精度加法
注意事项:
-
这里展示的加法只适用于正整数的相加。
-
运算时需要考虑进位。
接下来我们需要模拟以下图片的运算:
我们可以看到从个位开始,两个数开始相加,超过10之后就向前进位,直到最终计算完成。
显然,这样的运算所需要的次数是两个数中最大的位数。
现在,我们来模拟一下运算过程:
hp Add(const hp a, const hp b)
{
hp ans;
ans.init();
int len = a.len > b.len ? a.len : b.len;
for(int i = 0; i < len; i++)
{
ans.a[i] += a.a[i] + b.a[i];
if(ans.a[i] >= 10) // 进位处理
{
int tmp = ans.a[i] / 10;
ans.a[i] %= 10;
ans.a[i + 1] += tmp;
}
}
UpdateLen(ans); // 更新ans的长度
UpdateStr(ans); // 更新ans的str以便输出
return ans;
}
inline void UpdateLen(hp& a)
{
int i;
// 从高位开始数,确定位数,同时保证这个数为0时的长度为1
for(i = maxn - 1; i > 0 && a.a[i] == 0; i--);
a.len = i + 1;
}
inline void UpdateStr(hp& a)
{
for(int i = 0; i < a.len; i++)
a.str[i] = a.a[a.len - i - 1] + '0';
}
这样就完成了加法的模拟。
四、高精度减法
注意事项:
-
我们不知道哪个数才是大的数,所以输出的结果可能为负,我们需要加上符号的判定。
- 结构体增加符号
bool sign; // 符号,记得更新构造函数的初始化
- 增加比较大小的判定(该判定位于结构体内,也可以自行书写在外面)
int Compare(const hp x) { if(len < x.len) return -1; else if(len > x.len) return 1; else { for(int i = len - 1; i >= 0; i--) { if(a[i] < x.a[i]) return -1; else if(a[i] > x.a[i]) return 1; } } return 0; }
- 输出形式的修改
void Print() { if(!sign) printf("-"); printf("%s\n",str); }
-
需要进行借位操作的书写。
-
注意去掉高位运算出现的0。
模拟:
我们可以看到,有时最高位会出现为0的情况,我们使用的 UpdateLen 函数即可避免输出错误。
同样是从个位开始运算,接下来是代码:
hp Minus(hp a, hp b)
{
hp ans;
ans.init();
if(a.Compare(b) == -1) // 大的在后,则输出负数,交换位置
{
hp tmp = a;
a = b;
b = tmp;
ans.sign = false;
}
// 运算次数即为绝对值大的数的位数
for(int i = 0; i < a.len; i++)
{
ans.a[i] += a.a[i] - b.a[i];
while(ans.a[i] < 0) // 借位
{
ans.a[i] += 10;
ans.a[i + 1]--;
}
}
UpdateLen(ans); // 更新长度,同时处理高位0的情况
UpdateStr(ans);
return ans;
}
五、高精度乘法
注意事项:
-
该版本未考虑为负数的情况,如果需要请自行添加。
-
该乘法实现需要在封装了加法的基础上进行运算。
先看手动模拟:
-
不难看到,在第二个数的每一位跟第一个数相乘的时候,得出的数都进行了一个乘以 1 0 n 10^n 10n 的操作,即个位运算时乘以 1 0 0 10^0 100,十位运算时乘以 1 0 1 10^1 101……而这正好是存储这个数的数组的下标。
-
如果上面的描述难以理解,我们还可以通过乘法的分配律来理解:
123 × 456 = 123 × ( 400 + 50 + 6 ) = 123 × 400 + 123 × 50 + 123 × 6 123\times456 = 123\times(400 + 50 + 6) = 123\times400 + 123\times50 + 123\times6 123×456=123×(400+50+6)=123×400+123×50+123×6
- 运算过程中恰好产生了第二个数的位数个的数,例如第二个数为456,则产生3个这样的数。我们先将这些数用数组储存起来,最后再统一进行相加的操作,这样乘法就完成了。
接下来是代码:
hp Multi(const hp a, const hp b)
{
hp ans;
hp *tmp = new hp[b.len]; // 创建数组用于储存
ans.init();
for(int i = 0; i < b.len; i++) // 初始化数组
tmp[i].init();
for(int i = 0; i < b.len; i++)
{
/*j代表中途产生的数的第j + 1位,a.a[j - i]、b.a[i]分
别表示数字a、b中正在进行乘法运算的数,通过这样的错位来
达到乘10^n的目的。例如,现在进行的是123*456中的123*50,
此时i=1,若j=1,那么进行的就是3*5的运算,
则计算结果就会直接存储在第1位及以后,而第0位本来就是0,
我们就不需要管*/
for(int j = i; j < i + a.len; j++)
tmp[i].a[j] = a.a[j - i] * b.a[i];
UpdateLen(tmp[i]); // 更新位数以便进行加法运算
}
for(int i = 0; i < b.len; i++)
ans = Add(ans, tmp[i]); // 将全部加起来
delete []tmp; // 记得释放申请的空间
return ans;
}
六、高精度除法
注意事项:
-
该版本未考虑为负数的情况,如果需要请自行添加。
-
除法需要在封装了减法的基础上进行,同时需要比较大小的函数。
-
除法也存在高位0的情况。
还是先看模拟过程:
-
可以看到,总共需要进行 4 − 1 + 1 = 4 4-1+1=4 4−1+1=4 次运算才能得到结果。其中,第一次对3进行了 1 0 4 − 1 10^{4-1} 104−1 的操作, 1000 − 3000 1000-3000 1000−3000 为负数,则有效数字为0,余数为1000;第二次将3000变为了300, 1000 − 1200 1000-1200 1000−1200 为负数,则有效数字为3,余数为100,以此类推。
-
那么如何进行将3变成3000,又变成300、30、3的操作呢,我们可以仿照乘法中的做法,直接在需要的位置上进行赋值操作即可。例如,现在要将3变成3000,那么我们就只需要在数组对应千位的地方赋值3即可。
对应代码如下:
hp Move(hp a, int x) // x正为左移,负为右移
{
hp ans;
ans.init();
for(int i = 0; i < a.len + x; i++)
{
if(i - x >= 0)
ans.a[i] = a.a[i - x];
}
UpdateLen(ans);
UpdateStr(ans);
return ans;
}
hp Div(hp a, hp b)
{
hp ans;
ans.init();
if(a.Compare(b) != -1) // 如果a<b,结果直接为0
{
int pow = a.len - b.len;
hp cur = Move(b, pow);
for(int i = 0; i < pow + 1; i++, cur = Move(cur, -1))
{
ans = Move(ans, 1); // 乘10
// 一次一次的减,在结果为负的前一次停止
while(a.Compare(cur) >= 0)
{
a = Minus(a, cur);
ans.a[0]++;
}
}
}
UpdateLen(ans); // 更新位数,消除高位0
UpdateStr(ans); // 更新字符串
return ans; // 如果需要余数,那么此时a就是余数
}
七、完整代码
#include <cstdio>
#include <cstring>
const int maxn = 3e4 + 10;
struct hp
{
bool sign;
int a[maxn];
char str[maxn];
int len;
void init()
{
len = 0;
sign = true;
for(int i = 0; i < maxn; i++)
a[i] = str[i] = 0;
}
void Init()
{
scanf("%s",str);
len = strlen(str);
sign = true;
for(int i = len - 1; i >= 0; i--)
a[len - i - 1] = str[i] - '0';
}
void Print()
{
if(!sign)
printf("-");
printf("%s\n",str);
}
int Compare(const hp x)
{
if(len < x.len)
return -1;
else if(len > x.len)
return 1;
else
{
for(int i = len - 1; i >= 0; i--)
{
if(a[i] < x.a[i])
return -1;
else if(a[i] > x.a[i])
return 1;
}
}
return 0;
}
};
inline void UpdateLen(hp& a)
{
int i;
for(i = maxn - 1; i > 0 && a.a[i] == 0; i--);
a.len = i + 1;
}
inline void UpdateStr(hp& a)
{
for(int i = 0; i < a.len; i++)
a.str[i] = a.a[a.len - i - 1] + '0';
}
hp Add(const hp a, const hp b)
{
hp ans;
ans.init();
int len = a.len > b.len ? a.len : b.len;
int i;
for(i = 0; i < len; i++)
{
ans.a[i] += a.a[i] + b.a[i];
if(ans.a[i] >= 10)
{
int tmp = ans.a[i] / 10;
ans.a[i] %= 10;
ans.a[i + 1] += tmp;
}
}
UpdateLen(ans);
UpdateStr(ans);
return ans;
}
hp Multi(const hp a, const hp b)
{
hp ans;
hp *tmp = new hp[b.len];
ans.init();
for(int i = 0; i < b.len; i++)
tmp[i].init();
for(int i = 0; i < b.len; i++)
{
for(int j = i; j < i + a.len; j++)
tmp[i].a[j] = a.a[j - i] * b.a[i];
UpdateLen(tmp[i]);
}
for(int i = 0; i < b.len; i++)
ans = Add(ans, tmp[i]);
return ans;
}
hp Minus(hp a, hp b)
{
hp ans;
ans.init();
if(a.Compare(b) == -1)
{
hp tmp = a;
a = b;
b = tmp;
ans.sign = false;
}
for(int i = 0; i < a.len; i++)
{
ans.a[i] += a.a[i] - b.a[i];
while(ans.a[i] < 0)
{
ans.a[i] += 10;
ans.a[i + 1]--;
}
}
UpdateLen(ans);
UpdateStr(ans);
return ans;
}
hp Move(hp a, int x) // x正为左移,负为右移
{
hp ans;
ans.init();
for(int i = 0; i < a.len + x; i++)
{
if(i - x >= 0)
ans.a[i] = a.a[i - x];
}
UpdateLen(ans);
UpdateStr(ans);
return ans;
}
hp Div(hp a, hp b)
{
hp ans;
ans.init();
if(a.Compare(b) != -1)
{
int pow = a.len - b.len;
hp cur = Move(b, pow);
for(int i = 0; i < pow + 1; i++, cur = Move(cur, -1))
{
ans = Move(ans, 1);
while(a.Compare(cur) >= 0)
{
a = Minus(a, cur);
ans.a[0]++;
}
}
}
UpdateLen(ans);
UpdateStr(ans);
return ans;
}
int main()
{
hp a;
a.Init();
// b.Init();
// Add(a, b).Print();
int x;
scanf("%d",&x);
Move(a, x).Print();
return 0;
}