高精度算法

高精度的基本概念

概念: 高精度就是我们使用数组与字符串结合来表示大数字的方法。
原因: 在c++里面,每个变量都有其数据范围。

  • int 类型二进制位为32位,上线约为正负21亿。
  • long 类型在不同操作系统不太一样,在windows系统为32位,也是正负21亿,但一般不使用。
  • long long类型存较大的数字,二进制位为64位,上限为19位数字。
    可见不是所有的数字都能存下来,一旦我们的数字超过了19位数,就爆掉了,会报各种乱起八糟的错,因此我们在使用数字运算前要对数字大小进行一个预估,然后使用合适的数字类型。

引入

以一道例子来引入: 请计算一下100!有多大,
答案: 约为9 * 10^157
这么大的数字根本不可能用常规的数据类型来实现,这时我们就能用高精度代码来实现。
一下为演示代码,只作为引入,并没有题解。

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
#include<bits/stdc++.h>
using namespace std;
int a[10020],b[10020],c[10020];
string mul="1";
string multi(string x,int y)
{
memset(a,0,sizeof(a));
string res="";
int lena=x.size(),lenc=lena+5;
for(int i=0;i<lena;i++) a[i]=x[lena-1-i]-'0';
for(int i=0;i<lenc;i++) c[i]=a[i]*y;
if(c[lenc]==0) lenc--;
for(int i=0;i<lenc;i++){
while(c[i]>=10){
c[i+1]+=c[i]/10;
c[i]%=10;
}
}
while(c[lenc]==0&&lenc>0) lenc--;
for(int i=lenc;i>=0;i--)
res+=c[i]+'0';
return res;
}

int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
mul=multi(mul,i);
}
cout<<sum<<endl;
return 0;
}

这是一段高精度成低精度的典型例子,求阶乘。

详细教程

高精度加法:

流程

  1. 首先我们要处理输入问题,高精度的数字一般较大,无法用正常的变量接收,这是我们可以用可无限扩展的字符串来接收。
  2. 但是字符串不好处理,因此第二步我们用数组来存储数字(用vector也是可以的,道理是一样的,下面只演示静态数组),将两个待相加的数字都存起来,分别为A,B。
  3. 处理相加的过程,定义数组C,用来存我们相加的结果。
  4. 相加完处理进位问题。
  5. 最后输出结果。
    代码演示:
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
#include <bits/stdc++.h>
using namespace std;

int main() {
int a[2000] = {0}, b[2000] = {0}, c[4000] = {0};
string num1, num2;
cin >> num1 >> num2;
int len1 = num1.size();
int len2 = num2.size();
// 将字符串反转存储到数组a和b中(低位在前,方便我们相加)
for (int i = len1 - 1, j = 0; i >= 0; i--, j++) {
a[j] = num1[i] - '0';
}
for (int i = len2 - 1, j = 0; i >= 0; i--, j++) {
b[j] = num2[i] - '0';
}
// 处理进位
for (int i = 0; i < maxLen; i++) {
if (c[i] >= 10) {
c[i + 1] += c[i] / 10;
c[i] %= 10;
}
}
// 找到最高位非零的位置
int highIndex = maxLen;
while (highIndex > 0 && c[highIndex] == 0) {
highIndex--;
}
// 输出结果(从高位到低位)
for (int i = highIndex; i >= 0; i--) {
cout << c[i];
}
return 0;
}

高精度减法:

流程:

  1. 输入输出与高精度加法相同,后面不过多赘述。
  2. 高精度加法的实现与我们的常见的减法竖式运算相同
1
2
3
4
  86
- 79
————
7
  • 在这个算式里面,我们从低位开始减,不够向高位借1,然后高位开始减,以此类推。
  1. 用C数组来存我们的数字。
    代码实现:
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
#include<bits/stdc++.h>
using namespace std;

int main(){
4string num1;
4string num2;
4cin>>num1>>num2;
4int a[5005]={0};
4int b[5005]={0};
4int c[5005]={0};
4a[0]=num1.size(); //我们这里将每个数组的第一个数字也就是索引为0的地方存这个数字的位数
4b[0]=num2.size();
4c[0]=a[0];
4for(int i=1;i<=a[0];i++) a[i]=num1[a[0]-i]-'0'; //储存数字,倒叙存入
4for(int i=1;i<=b[0];i++) b[i]=num2[b[0]-i]-'0';
4for(int i=1;i<=a[0];i++){ //进行减法运算
44if(a[i]<b[i]){ //判断是否要进位
444a[i]+=10;
444a[i+1]--;
44}
44c[i]=a[i]-b[i]; //减法
4}
4while(c[c[0]]==0) c[0]--; //去除前导0
4for(int i=c[0];i>=1;i--) cout<<c[i]; //输出结果
4return 0;
}

高精度乘法:

流程:

  • 我们高精度乘法有两种思路,我建议使用类似贡献的思路,原理如下:
1
2
3
4
5
6
7
8
9
  18            18
19 19
----- -----
162 ---》 72
18 9
----- 8
342 1
-----
342
  • 这是一个简单的乘法,我们进行一个剖析:
  1. 首先我们将这个乘法里面每个数字都单独和上面的数字相乘,得到下面的四个数字,但是乘的结果是不一样的,有的数字位数天生就比别的位数高。
  2. 如果我们把个位看成1,那么第i位数字乘第j位数字得到结果就要加在第i+j-1上面。
  3. 这是个很容易发现的规律,没发现可以直接套用结论,不影响。
  4. 如果我们把个位看成0,结果就要加在第i+j上面
    代码实现:
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
#include<bits/stdc++.h>
using namespace std;
int main(){
4string num1,num2;
4cin>>num1>>num2;
4int a[5005];
4int b[5005];
4int c[10010]; //i和j位数相乘的结果最多是i+j位数字,提前预留好位置
4a[0]=num1.size();
4b[0]=num2.size();
4c[0]=a[0]+b[0];
4for(int i=1;i<=a[0];i++) a[i]=num1[a[0]-i]-'0'; //倒叙输入,a[0]存位数
4for(int i=1;i<=b[0];i++) b[i]=num2[b[0]-i]-'0';
4for(int i=1;i<=a[0];i++){ //双层for循环,遍历两个数字的所有位数的组合
44for(int j=1;j<=b[0];j++) c[i+j-1]+=a[i]*b[j]; //贡献思想,第i+j-1位数字由第i位和第j位数字决定。
4} //易错点,这上面的一定是+=,而不是=,不然我们后面的结果就会覆盖掉前面算的结果
4for(int i=1;i<=c[0];i++){
44if(c[i]>=10){
444c[i+1]+=c[i]/10;
444c[i]%=10;
44}
4}
4while(c[0]>0&&c[c[0]]==0) c[0]--;
4if(c[0]==0) cout<<0;
4for(int i=c[0];i>=1;i--) cout<<c[i];
4return 0;
}

低精度除法(多位小数):

流程:

  1. 低精度除法主要用于求多位小数,因为小数类型也是具有上限的,double也最多存15到16位数字,因此我们如果想求17位以上的小数就要用低精度除法。
  2. 我们用了除法的原理,每当除一个数后,结果等于整除的结果,留下的数为取余后的结果。
  3. 之后下一轮除法中,我们继续进行同样的操作,即可完成求多位小数。
    代码实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<bits/stdc++.h>
using namespace std; //低精度除以低精度,位数较多
int main(){
4int num1,num2;
4cin>>num1>>num2;
4int num=num1/num2; //得到整数部分
4int t=num1%num2; //得到余数部分
4int n;
4cin>>n; //输入想要得到的位数
4cout<<num<<".";
4while(n){
44n--;
44t*=10; //相当于余数后面加个0
44int ans=t/num2; //得到并输出结果
44cout<<ans;
44t%=num2; //将t变为除了num2之后的余数,之后继续进行循环
4}
4return 0;
}

高精度除法:

流程

  1. 高精度除法我们的思路与正常的除法思路大致相同,难点在于我们该怎么用代码实现。
  2. 首先我们要先把两个数存起来,之后在定义一个数组来存数字。
  3. 之后根据两个数:被除数和除数,现将两个数对齐,之后将被除数减去除数,然后将我们答案对于数字加一,如果还大,继续加一,直到对应位置小于除数。
  4. 随后除数整体往后移动一位。
    代码实现:
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include<bits/stdc++.h>
using namespace std;

void subs(int x[],int y[]){ //处理a数组和t数组之间的减法
//用来进行两个数的减法运算
4for(int i=x[0];i>=1;i--){
44if(x[i]<y[i]){
444x[i]=x[i]+10;
444x[i-1]=x[i-1]-1;
44}
44x[i]-=y[i];
4}
}

bool comp(int x[],int y[]){ //比较a(临时位数的情况下)和t数组哪个大
//用来进行同位数的比较大小
4for(int i=1;i<=x[0];i++){
44if(x[i]>y[i]) return true;
44if(x[i]<y[i]) return false;
4}
4return true;
}

void cpy(int x[],int y[],int offset){
//用来将一个数组加上偏移量,复制到另一个数组。主要是进行移位操作
4for(int i=1;i<=x[0];i++){
44y[i+offset]=x[i];
4}
4y[0]=x[0]+offset;
}
int main(){
4string num1;
4string num2;
4cin>>num1>>num2;
4int a[5005];
4int b[5005];
4int c[5005];
4a[0]=num1.size();
4b[0]=num2.size();
4c[0]=a[0]-b[0]+1;
4for(int i=1;i<=a[0];i++) a[i]=num1[i-1]-'0'; //正序存入数字方便除法
4for(int i=1;i<=b[0];i++) b[i]=num2[i-1]-'0';
4int t[10002]; //这个数组用来存移位后b数组,防止b数组被改变。
4for(int i=1;i<=c[0];i++){
44memset(t,0,sizeof(t)); //每次除完后就将t数组清空
44cpy(b,t,i-1); //清空后根据我们的移位,再将b数组复制到t数组对应位置
44a[0]=t[0]; //将a数组的临时位数与t相等,因为a数组后面的数暂时还不动。
44while(comp(a,t)){ //这一步用来减法,每次减完都给c数组对应位置+1.
444subs(a,t);
444c[i]++;
44}
4}
4if(c[1]!=0){ //输出答案防止前导0影响
44for(int i=1;i<=c[0];i++) cout<<c[i];
4}
4else{
44for(int i=2;i<=c[0];i++) cout<<c[i];
4}
4return 0;
}