高精度
高精度运算,是指参与运算的数或结果的范围大大超出了标准数据类型(整型,实型)
能表示的范围的运算。例如,求两个 200 位的整数的和。这时,就要用到高精度算法了。
常用的数据类型:
表 1 整型
数据类型 定义标识符 占字节数 数值范围 数值范围
整型 int 4(32 位)
-2147483648 ~
2147483647
-231 ~
231-1
超长整型 long
long
8(64 位)
-9223372036854775808~
9223372036854775807
-263 ~
263-1
表 2 实型
数据类型 定义标识符 占字节数 数值范围 有效位数
双精度实型 double 8(64 位) -1.7E+308~1.7E+308 15~16 位
(1)高精度数的输入和保存。
输入要符合数字的输入规律:连续输入,中间无空格。
可以使用字符串读入,数组保存。
char s1[300],s2[300];
int a[300],b[300],c[300];
scanf("%s",s1)或者 cin>>s;
如:s1=‘32456345’
保存: 3 2 4 5 6 3 4 5
a[8] a[7] a[6] a[5] a[4] a[3] a[2] a[1]
int la=strlen(s1); //s1 的长度
for(int i=1;i<=la;i++) a[i]=s1[la-i]-48; //保存读入的高精度数,
a[1]保存个位
(2)高精度数的输出
for (int i=lc;i>=1;i--) cout<<c[i];
2016 年山东省信息学奥赛夏令营讲义(2016.7.12-7.20) 举办学校:日照一中
83 / 105
【例题分析】
例 2.6.1 高精度加法运算
输入正整数 a 和 b,输出 a+b 的值。0<a,b<=10250
输入:
第一行:a
第二行:b
输出:a+b 的和。
样例输入:
99
999
样例输出:
1098
分析:
(1)加法运算
…… a[7] a[6] a[5] a[4] a[3] a[2] a[1]
…+… 0 0 b[5] b[4] b[3] b[2] b[1]
…… c[7] c[6] c[5] c[4] c[3] c[2] c[1]
运算的次数=max(la,lb)
(2)程序实现:
方法一:模拟手工计算,设置一个进位变量 m
#include<iostream>
#include<cstring>
using namespace std;
char s1[300],s2[300];
int a[300],b[300],c[300];
int main(){
int la,lb,lc,m=0;
cin>>s1>>s2;
la=strlen(s1);
lb=strlen(s2);
for (int i=1;i<=la;i++)
a[i]=s1[la-i]-48;
for (int i=1;i<=lb;i++)
b[i]=s2[lb-i]-48;
if (la>lb) lc=la;
else lc=lb;
for (int i=1;i<=lc;i++) {
c[i]=(m+a[i]+b[i])%10;
m=(m+a[i]+b[i])/10;
}
if (m==1) {lc++;c[lc]=1;}
2016 年山东省信息学奥赛夏令营讲义(2016.7.12-7.20) 举办学校:日照一中
84 / 105
for (int i=lc;i>=1;i--) cout<<c[i];
cout<<endl;
return 0;
}
方法二:先计算,最后处理进位
#include<iostream>
#include<cstring>
using namespace std;
char s1[300],s2[300];
int a[300],b[300],c[300];
int main(){
int la,lb,lc;
cin>>s1>>s2;
la=strlen(s1);
lb=strlen(s2);
for (int i=1;i<=la;i++)
a[i]=s1[la-i]-48;
for (int i=1;i<=lb;i++)
b[i]=s2[lb-i]-48;
if (la>lb) lc=la;
else lc=lb;
for (int i=1;i<=lc;i++) c[i]=a[i]+b[i];
for (int i=1;i<=lc;i++) {
c[i+1]=c[i+1]+c[i]/10;
c[i]=c[i]%10;
}
if (c[lc+1]==1) lc++;
for (int i=lc;i>=1;i--) cout<<c[i];
return 0;
}
思考:能不能把数组 c 去掉?
方法二改进:去掉 c 数组
for (int i=1;i<=la;i++) a[i]=a[i]+b[i];
for (int i=1;i<=la;i++) {
a[i+1]=a[i+1]+a[i]/10;
a[i]=a[i]%10;
}
if (a[la+1]==1) la++;
for (int i=la;i>=1;i--) cout<<a[i];
return 0;
例 2.6.1 拓展:大整数加法
求两个不超过 200 位的非负整数的和。
2016 年山东省信息学奥赛夏令营讲义(2016.7.12-7.20) 举办学校:日照一中
85 / 105
输入:
有两行,每行是一个不超过 200 位的非负整数,可能有多余的前导 0。
输出
一行,即相加后的结果。结果里不能有多余的前导 0,即如果结果是 342,那么就不能
输出为 0342。
样例输入
22222222222222222222
33333333333333333333
样例输出
55555555555555555555
题目来源:http://noi.openjudge.cn/ch0106/10/
分析:高精度加法,输出前考虑前导 0 的问题。
while (a[la]==0&&la>1)la--;
例 2.6.2 高精度减法运算
求两个大的正整数相减的差。
输入:
共 2 行,第 1 行是被减数 a,第 2 行是
2016 年山东省信息学奥赛夏令营讲义(2016.7.12-7.20) 举办学校:日照一中
86 / 105
int a[300],b[300];
int main(){
int la,lb,m=0;
cin>>s1>>s2;
la=strlen(s1);
lb=strlen(s2);
if (strcmp(s1,s2)==0) {cout<<0; return 0;}
if (la<lb||la==lb&&strcmp(s1,s2)<0) {
cout<<'-';
strcpy(s3,s1);
strcpy(s1,s2);
strcpy(s2,s3);
}
la=strlen(s1);
lb=strlen(s2);
for (int i=1;i<=la;i++)
a[i]=s1[la-i]-48;
for (int i=1;i<=lb;i++)
b[i]=s2[lb-i]-48;
for (int i=1;i<=la;i++) {
if (a[i]<b[i]) {
a[i+1]--;
a[i]+=10;
}
a[i]-=b[i];
}
while (a[la]==0&&la>1)la--;
for (int i=la;i>=1;i--) cout<<a[i];
cout<<endl;
return 0;
}
例 2.6.2 拓展:输入正整数 a 和 b,输出 a-b 的值。a,b<=10250
输入:
第一行:a
第二行:b
输出:a-b 的值。
样例输入:
33
100
样例输出:
-67
分析:
2016 年山东省信息学奥赛夏令营讲义(2016.7.12-7.20) 举办学校:日照一中
87 / 105
比较 a 和 b 的大小:从而确定结果的正负号
if (a==b) 结果 : 0
if (a>b) 结果 + :a-b
if (a<b) 结果 - :-(b-a)
la=strlen(s1);
lb=strlen(s2);
if (strcmp(s1,s2)==0) {cout<<0; return 0;}
if (la<lb||la==lb&&strcmp(s1,s2)<0) {如果 a<b,则交换,保证用大数
-小数
cout<<'-';
strcpy(s3,s1);
strcpy(s1,s2);
strcpy(s2,s3);
}
la=strlen(s1);
lb=strlen(s2);
程序实现:
#include<iostream>
#include<cstring>
using namespace std;
char s1[300],s2[300],s3[300];
int a[300],b[300];
int main(){
int la,lb,m=0;
cin>>s1>>s2;
la=strlen(s1);
lb=strlen(s2);
if (strcmp(s1,s2)==0) {cout<<0; return 0;}
if (la<lb||la==lb&&strcmp(s1,s2)<0) {
cout<<'-';
strcpy(s3,s1);
strcpy(s1,s2);
strcpy(s2,s3);
}
la=strlen(s1);
lb=strlen(s2);
for (int i=1;i<=la;i++)
a[i]=s1[la-i]-48;
for (int i=1;i<=lb;i++)
b[i]=s2[lb-i]-48;
for (int i=1;i<=la;i++) {
if (a[i]<b[i]) {
a[i+1]--;
2016 年山东省信息学奥赛夏令营讲义(2016.7.12-7.20) 举办学校:日照一中
88 / 105
a[i]+=10;
}
a[i]-=b[i];
}
while (a[la]==0&&la>1)la--;
for (int i=la;i>=1;i--) cout<<a[i];
cout<<endl;
return 0;
}
例 2.6.3 高精度乘单精度
求 a=a*b。(0<a<10250, 0<b<108)
输入:
第一行:a
第二行:b
输出:a*b 的值。
样例输入:
330
100
样例输出:
33000
分析:
1)a 的每一位都单独与 b 相乘;
2)再由低到高位依次处理 a 的进位;
3)最后处理最高位。
程序实现:
#include<iostream>
#include<cstring>
using namespace std;
char s1[300],s2[300];
int a[300];
int main(){
int la,b,m;
cin>>s1>>b;
la=strlen(s1);
for (int i=1;i<=la;i++) a[i]=s1[la-i]-48;
for (int i=1;i<=la;i++) a[i]=a[i]*b;
for (int i=1;i<=la;i++) {//处理进位
a[i+1]=a[i+1]+a[i]/10;
a[i]=a[i]%10;
}
m=a[la+1];
while (m>0){a[++la]=m%10;m=m/10;}//处理最高位
2016 年山东省信息学奥赛夏令营讲义(2016.7.12-7.20) 举办学校:日照一中
89 / 105
for (int i=la;i>=1;i--) cout<<a[i];
return 0;
}
例 2.6.4 高精度乘高精度
求 c=a*b(0<a<10250, 0<b<10250)
输入:
第一行:a
第二行:b
输出:a-b 的值。
样例输入:
22222
33333
样例输出:
740725926
分析:
假设乘数和被乘数的长度分别为 la 和 lb,则最后的乘积长度最长为 la+lb,最短为
la+lb-1;
程序实现:边乘边处理进位
#include<iostream>
#include<cstring>
using namespace std;
int a[300],b[300],c[300];
char s1[300],s2[300],s3[300];
int main(){
cin>>s1>>s2;
int la,lb,len;
la=strlen(s1);
lb=strlen(s2);
for(int i=1;i<=la;i++) a[i]=s1[la-i]-48;
for(int i=1;i<=lb;i++) b[i]=s2[lb-i]-48;
for(int i=1;i<=la;i++) //a[i]*b[j]的结果存放在 c[i+j-1]
for(int j=1;j<=lb; j++){
c[i+j-1]=c[i+j-1]+a[i]*b[j];
c[i+j]=c[i+j]+c[i+j-1]/10;
c[i+j-1]=c[i+j-1]%10;
}
len=la+lb;
while (c[len]==0&&len>1) len--; //去掉多余的前导 0;
for(int i=len;i>=1;i--) cout<<c[i];
return 0;
}
【高精度应用】
2016 年山东省信息学奥赛夏令营讲义(2016.7.12-7.20) 举办学校:日照一中
90 / 105
例 2.6.5 回文数(NOIP1999 简化版)
若一个数(首位不为零)从左向右读与从右向左读都一样,我们就将其称之为回文
数。
例如:给定一个 10 进制数 56,将 56 加 65(即把 56 从右向左读),得到 121 是一个
回文数。
又如:对于 10 进制数 87:
STEP1:87+78 = 165 STEP2:165+561 = 726
STEP3:726+627 = 1353 STEP4:1353+3531 = 4884
在这里的一步是指进行了一次 N 进制的加法,上例最少用了 4 步得到回文数 4884。
输入:
第一行是整数 N(2<=N<=10),表示这个数的进制;
第二行是 N 进制数 M,求最少经过几步可以得到回文数。
输出:
若最少在 30 步以内得到回文数,则输出步数;
如果在 30 步以内(包含 30 步)不可能得到回文数,则输出“Impossible”(无引号)
样例输入:
9
87
样例输出:
6
分析:
(1)本题数值虽然不大,但是为了处理方便可以采用字符串读入,数组保存。
(2)N 进制的加法:逢 n 进 1
程序实现:
#include<iostream>
#include<cstring>
using namespace std;
char s1[300],s2[300];
int a[300],b[300];
int main(){
int n,len,cnt=0,t=0,ok;
cin>>n>>s1;
len=strlen(s1);
for (int i=1;i<=len;i++)
a[i]=s1[len-i]-48;
while (t<=30){
t++;
for (int i=1;i<=len;i++) b[i]=a[i]+a[len-i+1];
for (int i=1;i<=len;i++) {
b[i+1]=b[i+1]+b[i]/n;
b[i]=b[i]%n;
}
if (b[len+1]==1) len++;
ok=0;
2016 年山东省信息学奥赛夏令营讲义(2016.7.12-7.20) 举办学校:日照一中
91 / 105
for (int i=1;i<=len/2;i++)
if (b[i]!=b[len-i+1]) {ok=1;break;}
memcpy(a,b,300*sizeof(int));
if (ok==0) break;
}
if (ok==0) cout<<t<<endl;
else cout<<"Impossible"<<endl;
return 0;
}
例 2.6.5 斐波那契数列
斐波那契数列,指的是这样一个数列:0、1、1、2、3、5、8、13、21、……在数学
上,斐波纳契数列以如下的方法定义:
F0=0,F1=1,Fn=F(n-1)+F(n-2)(3<=n<=1000)
输入正整数 n, 求该数列第 n 项的值。
输入样例 1:20
输出样例 1:4181
输入样例 2:40
输出样例 2: 63245986
输入样例 3:50
输出样例 3: 7778742049
分析:高精度+高精度
程序实现:
#include<iostream>
#include<cstring>
using namespace std;
int a[1000],b[1000],c[1000];
int main(){
int n;
cin>>n;
a[1]=1;a[0]=1;
b[1]=1;b[0]=1;
int len;
for (int k=3;k<=n;k++) {
len=b[0];
for (int i=1;i<=len;i++) c[i]=a[i]+b[i];
for (int i=1;i<=len;i++) {
c[i+1]=c[i+1]+c[i]/10;
c[i]=c[i]%10;
}
if (c[len+1]==1) len++;
c[0]=len;
memcpy(a,b,1000*sizeof(int));memcpy(b,c,1000*sizeof(int));
2016 年山东省信息学奥赛夏令营讲义(2016.7.12-7.20) 举办学校:日照一中
92 / 105
}
for(int i=c[0];i>=1;i--) cout<<c[i];
return 0;
}
例 2.6.6 计算 2 的 N 次方
任意给定一个正整数 N(N<=100),计算 2 的 n 次方的值。
输入:一个正整数 N。
输出:2 的 N 次方的值。
样例输入:5
样例输出:32
题目来源:http://noi.openjudge.cn/ch0106/12/
分析:
2100 约等于 1030,运算结果超过了标准数据类型的范围,所以采用高精度运算(高精
度*单精度)。
程序实现:
#include<iostream>
#include<cstring>
using namespace std;
int a[300];
int main(){
int n,la=1;
cin>>n;
a[1]=1;
for (int j=1;j<=n;j++) {
for (int i=1;i<=la;i++) a[i]=a[i]*2;
for (int i=1;i<=la;i++) {
a[i+1]=a[i+1]+a[i]/10;
a[i]=a[i]%10;
}
int m=a[la+1];
while (m>0){a[++la]=m%10;m=m/10;}//处理最高位
}
for (int i=la;i>=1;i--) cout<<a[i];
cout<<endl;
return 0;
}
例 2.6.7 求 10000 以内 n 的阶乘
输入:输入整数 n(0<=n<=10000)。
输出:一行,即 n!的值。
样例输入
100
样例输出
93326215443944152681699238856266700490715968264381621468592963
2016 年山东省信息学奥赛夏令营讲义(2016.7.12-7.20) 举办学校:日照一中
93 / 105
89521759999322991560894146397615651828625369792082722375825118521
0916864000000000000000000000000
来源 http://noi.openjudge.cn/ch0106/14/
分析:高精度*单精度
程序实现:
#include<cstdio>
#include<iostream>
using namespace std;
int a[100000];
int main(){
int n,la=1;
cin>>n;
a[1]=1;//初值
for (int j=1;j<=n;j++){
for (int i=1;i<=la;i++) a[i]=a[i]*j;
for (int i=1;i<=la;i++) {//处理进位
a[i+1]=a[i+1]+a[i]/10;
a[i]=a[i]%10;
}
int m=a[la+1];
while (m>0){a[++la]=m%10;m=m/10;}//处理最高位
}
for (int i=la;i>=1;i--) cout<<a[i];
return 0;
}