我好羡慕会用java的人
为什么要用到高精度呢?
我们知道,$int$的范围是$\pm2^{31}-1$,$long,long$的范围是$\pm2^{63}-1$,那么当我们想要表示更往上的数字,应该怎么做?虽然,我已经学会了
我上小学
很计算机的一种方式,将每一位放在一个$a[i]$中,这样,一个数字就变成一个数组,对数字的四则运算,也就变成了对数组的操作.
高精度加法
问:$1234+5678$答案是多少?答:$我不知道$
咳咳,按照小学的教法,我们知道,要列个竖式,对齐数位,一位一位相加,满$10$进$1$.
于是:
$$\quad\quad1234\\underline{,\quad+5678}\\quad\quad6912$$
分析一下计算过程,我们发现,当我们用数组$a$,数组$b$,分别存下$1234$和$5678$后,从数组的最后一位开始$for$循环,用数组$S$保存和,$temp$保存进位
可以得到
再将这个过程转化为代码,高精度加法就写出来了
1 | BigNum BigNum::operator+(const BigNum &i_T)const //BigNum+BigNum |
高精度减法
众所周知,减法是加法的逆运算.所以,我们将加法的过程反过来就是减法.
- 从头往后处理
- $temp$保存向后一位的借位
- 处理负数的偷懒方式为,将第一位前加个符号,输出的时候就加上了符号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38BigNum BigNum::operator-(const BigNum &i_T)const //num - num
{
int big, j;
bool flag;
BigNum t1, t2;
if (*this > i_T) {
t1 = *this;
t2 = i_T;
flag = 0;
}
else {
t1 = i_T;
t2 = *this;
flag = 1;
}
big = t1.len;
for (int i = 0; i < big; i++) {
if (t1. a[i] < t2.a[i]) {
j = i + 1;
while (t1.a[j] == 0)
j++;
t1.a[j--]--;
while (j > i)
t1.a[j--] += MAXN;
t1.a[i] += MAXN + 1 - t2.a[i];
}
else
t1.a[i] -= t2.a[i];
}
t1.len = big;
while (t1.a[t1.len - 1] == 0 && t1.len > 1) {
t1.len--;
big--;
}
if (flag)
t1.a[big - 1] = 0 - t1.a[big - 1];
return t1;
}
高精度乘法
我们现在还是小学
提问:$1234\times5678$答案是多少
我们来列个式杂
$\quad\quad\quad1234$
$;\quad\underline{\quad\times5678}$
$\quad\quad\quad9872$
$\quad\quad8638$
$\quad;;7404$
$\underline{\quad6170\quad;}$
$\quad7006652$
那么分析一下这个过程.
设一个空的$s$数组,$b$数组的个位$\times a$从个位开始和$s$的每一位相加,$b$数组的十位$\times a$从十位开始和$s$的每一位相加,以此类推,一直到$b$的千位计算结束,得到的便是答案
1 | BigNum BigNum::operator*(const BigNum &i_T)const |
高精度除法
好的,我们现在还是小学生
高精度除低精度
好的,首先,除法是乘法的逆元,所以我们~
倒着做回去
- 从头往后处理
- $down$储存余数
- 当余数+该位小于低精度的数时,我们向后延续一位
1
2
3
4
5
6
7
8
9
10
11
12
13BigNum BigNum::operator/(const int &i_b)const
{
BigNum ret;
int down = 0;
for (int i = len - 1; i >= 0; i--) {
ret.a[i] = (a[i] + down * (MAXN + 1)) / i_b;
down = a[i] + down * (MAXN + 1) - ret.a[i] * i_b;
}
ret.len = len;
while (ret.a[ret, len - 1] == 0 && ret.len > 1)
ret.len--;
return ret;
}
完整代码
1 |
|
高精度除高精度
好的,我们现在不做小学生了
考虑用除低精度的做法,太麻烦了.
那么我们再回到那句话,除法是乘法的逆元.
考虑:
$a/b=c\ldots d$,那么也就意味着$d+c\times b=a$,那么只要找到一个数$c$,使得$c\times b+d=a$即可
于是,问题变为了加法和乘法的组合.
对于加法,一个个试的话,必定超时.
考虑两种方式:二分法和牛顿法.
高精度用不了牛顿法况且我也不会,使用二分法,复杂度为$O(logN)$.
对于乘法
- 普通的模拟$O(N^2)$.
- 分治乘法:最简单的是$Karatsuba$乘法,一般化以后有$Toom-Cook$乘法;
- 快速傅里叶变换$FFT$:(为了避免精度问题,可以改用快速数论变换$FNTT$),时间复杂度$O(N lgN lglgN)$。参照$Schönhage–Strassen algorithm$和$Fürer’s algorithm$
- 中国剩余定理:把每个数分解到一些互素的模上,然后每个同余方程对应乘起来就行
两者结合即可解决问题.
$fft$的话可以看一下$hdu1402$
$java$$AC$后,$c/c++$还在敲代码.