高精度算法

高精度算法

1.什么是高精度?

  高精度是一种处理大数字的数学计算方法。在一些特殊的的计算场合中,经常会算到小数点后几百位甚至更多,当然也可能是大几千亿几百亿的数字。我们把这类数字统称为高精度数字。高精度算法就是计算机对于超大数据的一种模拟加减乘除乘方阶乘开方等运算。
  对于一些非常庞大的数字,我们没有办法在计算机中正常存储,于是我们可以将这个数字拆开,拆成一位一位的,或者是几位几位的存储到一个数组中去,用一个数组表示一个数,这样这个数就被称为高精度数。高景素算哒就是能处理高精度数各种运算的算法,又因为它运算的特殊性,我们把它单独拿出来。
  在处理这类问题中,自然用long或者double这些数据类型是无法存下的,我们可以把这两个数当作字符串输入到数组中,然后模拟手动的竖式运算得出计算结果。

2.高精度的作用

   正如上面所说,高精度的作用就是对于一些异常大的数字进行加减乘除乘方阶乘开方等运算。比如给你一道a+b的题目,读入a和b,让你输入它们的和,但a和b的范围都是小于等于10的6666次方,此时我们就只能使用高精度算法了。

3.高精度读入读出数据
  当一个数据很大时,我们无法使用整数类型存储,所以我们把它当成一个字符串输入,这样就可以输入一个很长的数,然后再利用字符串函数的某些运算,将每一位数字取出,存放进数组里。这里我们用数组里的每一位表示这个数的每一个数位。

    在这里,我们是倒着存储这些数字的。可能有的同学会有疑问,为什么一定要倒着存储这个数呢?顺着存不是更好吗?这里我们来举个例子,比如10+90=100,那么我们来看一看二者的区别:
顺着存:
a={1,0},b={9,0},当a的最到位(即数组第1位)加上b的最高位(即数组的第1位),我们在进位的时候发现,a的最高位是数组的第一位,我们没有办法再往前存储了。如果要把所有的位置往右移动一位,时间复杂度会大大的增高,有一点得不偿失了。
倒着存:
a={0,1},b={0,9},当a的最高位(即数组的第2 位)加上b的最高位(即数组的第2位)时,我们同样要进位,这个时候就好办了,直接进位到数组的第三位就好了,所以答案数组就是{0,0,1},倒过来输出答案100。

高精度读入input

char str[6666];
int a[6666];

int main()
{
    cin >>  str ;
    int len = strlen(str);
    for(int i=1;i<=len;i++){
        a[i]=str[len-i]-'0';
    }//倒叙存储
}

高精度输出output

int a[6666];
void write(int a[])
{
    for(int i=lena;i>0;i--)
        cout<<a[i];
}

高精度比较大小
    数据处理完之后,我们就需要对两个数据进行比较。
    假设我们比较1234560和1234570,首先我们先比较两个数据的长度(即lena和lenb),如果哪个数的len更长,那么这个数一定是较大的一个;否则我们就继续进行比较。此时举例的两个数字长度是一样的,所以接下来我们就看这两个数的最高位,如果相等,就继续比较下一位…直到比出不相等的一位为止,直接退出。

所以我们来整理一下具体步骤:
1.比较两个数的长度,长度越长,数字越大。
2.如果两个数字的长度相等,那么就从高位到低位一位一位的进行比较,如果某一位数字不同时,较大的一方大。否则继续比较下一位。
3.如果比到最后都没有判断出谁大谁小,就说明这两个数一样大。

//高精度比较大小
int a[6666],b[6666];
int compare(){
	if(lena>lenb) return 1;
	else if(lenb>lena) return 0;//步骤1
	for(int i=lena ;i>0;i--){
		if(a[i]>b[i]) return 1;
		else if(b[i]>a[i]) return 0;
	}//步骤2
}

处理进位与借位

进位
    我们来模拟一下,1234+888的时候,首先我们把最低位相加,得到12,那么答案的最低为就是2,往上一位进个1,然后两数的十位相加,3+8=11,再加上进位的1,就是11+1=12,所以答案十位就是2,再往前进1,2+8+1=11,答案百位是1,进1,1+0+1=2,答案千位是2.所以答案是2122。
进位的大致步骤:
1.将当前的位置数相加,当前位的结果就是当前结果除以10 的余数。
2.再将当前的结果除以10加到高位,表示进位。

int a[6666],b[6666],c[6666];
int lena,lenb,lenc;

int main()
{
	lenc=max(lena,lenb);
	for(int i=1;i<=lenc;i++)
	{
		c[i]+=a[i]+b[i];
		c[i+1]=c[i]/10;
		c[i]%=10;
	}
	while(c[lenc+1]>0) lenc++;
	//答案的长度在某些情况下还会增加。
}

借位
接下来想一下计算减法的时候,如何进行借位。
1.将当前位置的数相减
2.如果结果大于等于0,则就可以直接当作当前位置的答案。
3.如果结果小于0,则把结果+10当作当前位的答案,然后将高位的数-1即可。

int a[6666],b[6666],c[6666];
int lena,lenb,lenc;
int main()
{
    lenc=max(lena,lenb);
    for(int i=1;i<=lenc;i++)
    {
    	c[i]=c[i]+a[i]-b[i];
    	if(c[i]<0) c[i]=c[i]+10;
    	c[i+1]=-1;
    }
    while(c[lenc]==0&&lenc>1) lenc--;
    //细节,如果a-b为0,那么也要输出一个0;
}

高精度加

    到这里为止,我们就可以进行一些基本的运算了,首先来看假发,根据上面代码部分的解释,我们就已经差不多可以写出来了。

#include<bits/stdc++.h>
using namespace std;
int a[6666]={0},b[6666]={0},c[6666]={0};
string stra,strb;
int lena,lenb,lenc;
int compare()
{
	if(lena>lenb) return 1;
	else return 0;
	for(int i=max(lena,lenb) ; i>0 ; i--)
	{
	    if(a[i]>b[i]) return 1;
	    else if(b[i]<a[i]) return 0;
	}
}
int main()
{ 
    cin >>stra>>strb;
	lena = stra.length();
	lenb = strb.length();
	lenc=max(lena,lenb);
	for(int i=1;i<=lena;i++)
		a[i]=stra[lena-i]-'0';
	for(int i=1;i<=lenb;i++)
		b[i]=strb[lenb-i]-'0';
	for(int i=1;i<=lenc;i++)
	{
		c[i]=c[i]+a[i]+b[i];
		c[i+1]=c[i]/10;
		c[i]=c[i]%10;
	}
	if(c[lenc+1]>0) lenc++;
	for(int i=lenc;i>0;i--)
	{
		cout<<c[i];
	}
} 

高精度减

#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
int a[6666],b[6666],c[6666];
int lena,lenb,lenc;
char s1[6666],s2[6666];
 
int main(){
    cin >> s1>>s2;
    lena=strlen(s1+1);
    lenb=strlen(s2+1);
    if(lenb>lena||(lena==lenb&&s2>s1)){//如果第二个数比第一个数大,那么结果是负数
    	printf("-");
    	swap(s1,s2);//swap是C++自带函数可以直接调用
    	swap(lena,lenb);//别忘了交换长度
    }
    for(int i=1;i<=lena;i++) a[i]=s1[lena-i+1]-'0';
    for(int i=1;i<=lenb;i++) b[i]=s2[lenb-i+1]-'0';
    lenc=max(lena,lenb);
    for(int i=1;i<=lenc;i++){
        c[i]=c[i]+a[i]-b[i];
        if(c[i]<0) c[i]=c[i]+10,c[i+1]=-1;
    }
    while(c[lenc]==0&&lenc>1) lenc--;
    for(int i=lenc;i>0;i--) cout<<c[i];
}

高精度乘

高精度×低精度

#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
int a[6666],b;
int lena;
char s1[6666];
 
int main(){
    cin>>s1>>b;
    lena=strlen(s1+1);
    for(int i=1;i<=lena;i++) a[i]=s1[lena-i+1]-'0';
    for(int i=1;i<=lena;i++) a[i]*=b;
    for(int i=1;i<=lena;i++){
    	a[i+1]+=a[i]/10;
    	a[i]%=10;
    }
    while(a[lena+1]>0) lena++;
    for(int i=lena;i>0;i--) cout<<a[i];
}

高精度×高精度

#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
int a[6666],b[6666],c[6666];
int lena,lenb,lenc;
char s1[6666],s2[6666];
 
int main(){
    cin>>s1>>s2;
    lena=strlen(s1+1);
    lenb=strlen(s2+1);
    for(int i=1;i<=lena;i++) a[i]=s1[lena-i+1]-'0';
    for(int i=1;i<=lenb;i++) b[i]=s2[lenb-i+1]-'0';
    lenc=lena+lenb-1;
    for(int i=1;i<=lena;i++){
    	for(int j=1;j<=lenb;j++){
    		c[i+j-1]+=a[i]*b[j];
    		c[i+j]+=c[i+j-1]/10;
    		c[i+j-1]%=10;
		}
	}
    while(c[lenc+1]>0) lenc++;
    for(int i=lenc;i>0;i--) cout<<c[i];
}

高精度除

    我们还是模拟一下,记录一个r,表示当前的余数,然后不停的去除就可以了。
高精度÷单精度


#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
int a[6666],b,r;
int lena;
char s1[6666];
 
int main(){
    cin>>s1>>b;
    lena=strlen(s1+1);
    for(int i=1;i<=lena;i++) a[i]=s1[lena-i+1]-'0';
    for(int i=lena;i>0;i--){
    	r=r*10+a[i];
    	a[i]=r/b;
    	r=r%b;
    }
    while(a[lena]==0&&lena>1) lena--;
    for(int i=lena;i>0;i--) cout<<a[i];
}

高精度÷高精度
    因为除法是乘法的逆运算,也可以看作是我们现在要求一个数乘以除数等于被除数。那么枚举肯定是不行的,所以我们这里可以使用二分法,也就是高精度二分商。实际上就是先进行一次高精度加法,然后再进行高精度除以单精度,最后再用高精度减法就可以实现二分了。我们用二分出来的数直接用高精度×高精度来判断一下就可以了。
    其中数组中0号位置表示这个数的长度。

#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
int a[6666],b[6666],ans[6666];
int t[6666],l[6666],r[6666],mid[6666];
char s1[6666],s2[6666];
 
int compare(int a[],int b[]){
    if(a[0]>b[0]) return 0;
    if(a[0]<b[0]) return 1;
    for(int i=a[0];i>0;i--){
        if(a[i]>b[i]) return 0;
        if(a[i]<b[i]) return 1;
    }
    return 1;
}
void add(){
    mid[0]=max(l[0],r[0]);
    for(int i=1;i<=mid[0];i++) mid[i]=l[i]+r[i];
    while(mid[mid[0]+1]>0) mid[0]++;
    for(int i=1;i<=mid[0];i++){
        mid[i+1]+=mid[i]/10;
        mid[i]%=10;
    }
    while(mid[mid[0]+1]>0) mid[0]++;
}
 
void times(){
    memset(t,0,sizeof(t));
    t[0]=b[0]+mid[0]-1;
    for(int i=1;i<=b[0];i++){
        for(int j=1;j<=mid[0];j++){
            t[i+j-1]+=b[i]*mid[j];
            t[i+j]+=t[i+j-1]/10;
            t[i+j-1]%=10;
        }
    }
    while(t[t[0]+1]>0) t[0]++;
}
 
void div(){
    int r=0;
    for(int i=mid[0];i>0;i--){
        r=r*10+mid[i];
        mid[i]=r/2;
        r%=2;
    }
    while(mid[mid[0]]==0) mid[0]--;
}
 
void left(){
    for(int i=0;i<=mid[0];i++) l[i]=ans[i]=mid[i];
    l[1]++;
    for(int i=1;i<=l[0];i++){
        l[i+1]+=l[i]/10;
        l[i]%=10;
    }
}
 
void right(){
    for(int i=0;i<=mid[0];i++) r[i]=mid[i];
    r[1]--;
    for(int i=1;i<=r[0];i++){
        if(r[i]<0){
            r[i+1]--;
            r[i]+=10;
        }
    }
}
 
int main(){
    cin>>s1>s2;
    a[0]=r[0]=strlen(s1+1);
    b[0]=strlen(s2+1);
    for(int i=1;i<=a[0];i++) a[i]=r[i]=s1[a[0]-i+1]-'0';
    for(int i=1;i<=b[0];i++) b[i]=s2[b[0]-i+1]-'0';
    l[0]=ans[0]=1;
    while(compare(l,r)){
        add();
        div();
        times();
        if(compare(t,a)) left();
        else right();
    }
    for(int i=ans[0];i>0;i--) cout<<ans[i];
    return 0;
}

高精度开平方

    高精度开平方其实也还算比较简单,这里我还是选择二分。
利用高精度加法和高精度除以单精度就可以实现二分的效果,然后直接用高精度乘法乘起来,再比较一下大小,最后用高精度剪法移动一下l和r就可以了。
    其实这也算是高精度比较综合的做法了。

#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
int a[6666],b[6666],ans[6666],t[6666],l[6666],r[6666],mid[6666];
char s1[6666],s2[6666];
 
int compare(int a[],int b[]){
    if(a[0]>b[0]) return 0;
    if(a[0]<b[0]) return 1;
    for(int i=a[0];i>0;i--){
        if(a[i]>b[i]) return 0;
        if(a[i]<b[i]) return 1;
    }
    return 1;
}
 
void add(){
    mid[0]=max(l[0],r[0]);
    for(int i=1;i<=mid[0];i++) mid[i]=l[i]+r[i];
    while(mid[mid[0]+1]>0) mid[0]++;
    for(int i=1;i<=mid[0];i++){
        mid[i+1]+=mid[i]/10;
        mid[i]%=10;
    }
    while(mid[mid[0]+1]>0) mid[0]++;
}
 
void times(){
    memset(t,0,sizeof(t));
    t[0]=mid[0]+mid[0]-1;
    for(int i=1;i<=mid[0];i++){
        for(int j=1;j<=mid[0];j++){
            t[i+j-1]+=mid[i]*mid[j];
            t[i+j]+=t[i+j-1]/10;
            t[i+j-1]%=10;
        }
    }
    while(t[t[0]+1]>0) t[0]++;
}
 
void div(){
    int r=0;
    for(int i=mid[0];i>0;i--){
        r=r*10+mid[i];
        mid[i]=r/2;
        r%=2;
    }
    while(mid[mid[0]]==0) mid[0]--;
}
 
void left(){
    for(int i=0;i<=mid[0];i++) l[i]=ans[i]=mid[i];
    l[1]++;
    for(int i=1;i<=l[0];i++){
        l[i+1]+=l[i]/10;
        l[i]%=10;
    }
}
 
void right(){
    for(int i=0;i<=mid[0];i++) r[i]=mid[i];
    r[1]--;
    for(int i=1;i<=r[0];i++){
        if(r[i]<0){
            r[i+1]--;
            r[i]+=10;
        }
    }
}
 
int main(){
    cin>>s1;
    a[0]=r[0]=strlen(s1+1);
    for(int i=1;i<=a[0];i++) a[i]=r[i]=s1[a[0]-i+1]-'0';
    l[0]=ans[0]=1;
    while(compare(l,r)){
        add();
        div();
        times();
        if(compare(t,a)) left();
        else right();
    }
    for(int i=ans[0];i>0;i--) cout<<ans[i];
    return 0;
}

高精度开n次方

    稍加处理:开平方时将二分的答案乘两次,开出的n次方就是将二分出来的答案乘n次。

#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
int n,a[6666],b[6666],ans[6666],t[6666],l[6666],r[6666],mid[6666];
char s1[6666];
 
int compare(int a[],int b[]){
    if(a[0]>b[0]) return 0;
    if(a[0]<b[0]) return 1;
    for(int i=a[0];i>0;i--){
        if(a[i]>b[i]) return 0;
        if(a[i]<b[i]) return 1;
    }
    return 1;
}
 
void add(){
    mid[0]=max(l[0],r[0]);
    for(int i=1;i<=mid[0];i++) mid[i]=l[i]+r[i];
    while(mid[mid[0]+1]>0) mid[0]++;
    for(int i=1;i<=mid[0];i++){
        mid[i+1]+=mid[i]/10;
        mid[i]%=10;
    }
    while(mid[mid[0]+1]>0) mid[0]++;
}
 
void times(){
    for(int i=0;i<=mid[0];i++) b[i]=mid[i];
    for(int k=1;k<=n-1;k++){
        memset(t,0,sizeof(t));
        t[0]=b[0]+mid[0]-1;
        for(int i=1;i<=b[0];i++){
            for(int j=1;j<=mid[0];j++){
                t[i+j-1]+=b[i]*mid[j];
                t[i+j]+=t[i+j-1]/10;
                t[i+j-1]%=10;
            }
        }
        while(t[t[0]+1]>0) t[0]++;
        for(int i=0;i<=t[0];i++) b[i]=t[i];
    }
}
 
void div(){
    int r=0;
    for(int i=mid[0];i>0;i--){
        r=r*10+mid[i];
        mid[i]=r/2;
        r%=2;
    }
    while(mid[mid[0]]==0) mid[0]--;
}
 
void left(){
    for(int i=0;i<=mid[0];i++) l[i]=ans[i]=mid[i];
    l[1]++;
    for(int i=1;i<=l[0];i++){
        l[i+1]+=l[i]/10;
        l[i]%=10;
    }
}
 
void right(){
    for(int i=0;i<=mid[0];i++) r[i]=mid[i];
    r[1]--;
    for(int i=1;i<=r[0];i++){
        if(r[i]<0){
            r[i+1]--;			
            r[i]+=10;
        }
    }
}
 
int main(){
    cin>>n;
    cin>>s1;
    a[0]=r[0]=strlen(s1+1);
    for(int i=1;i<=a[0];i++) a[i]=r[i]=s1[a[0]-i+1]-'0';
    l[0]=ans[0]=1;
    while(compare(l,r)){
        add();
        div();
        times();
        if(compare(t,a)) left();
        else right();
    }
    for(int i=ans[0];i>0;i--) printf("%d",ans[i]);
    return 0;
}

高精度压位

    以上基本就是所有的高精度运算,但是还是有一个问题,就是它的数会很大很大,以至于我们一位一位运算时可能会导致时间超时。
    解决这个问题的方法就是压位。我们发现,高精度时间慢的原因是因为我们一位一位的将这个数存储下来,那么我们就可以在数组中的一个位置不止存一个数位,可以存两位,三位…其实我们压位的时候最少要压十几位。
    当我们压位的时候,如果你要压比如16位,那么一定要记得开long long或int64类型,这样你才存的下这个十几位的数,还要注意,当我们输出这个数的时候,当一个位置的数不满你压的位数时,比如你压了16位,但是数组当前的位置的这个数只有1位!!!这说明了什么?说明这个数是由前面的15个0和最后一位构成的,也就是说原来这个数是000000000000000X,但是你只输出了X,明显答案是不对的,比如说一个数是5200000000000000001314,那么你存的是a{ 1314,520000 },当输出的时候如果你只是按照数组中的数字输出,那么你的答案是5200001314,发现了吗?你少了中间的0!!!所以当我们输出的时候,我们要判断这个数是否是满你压的位数,如果不足,那么就要补0,但是最高位除外。这就是压位的基本思路了。
压位读入

const int mod=1000000000000000;
char s[6666];
int a[6666];
 
 
int main(){
    scanf("%s",s+1);
    int len=strlen(s+1),st=1;
    for(int i=len;i>0;i--){
        x=x+(s[i]-'0')*st;
        st*=10;
        if(st==mod){
            st=1;x=0;
            a[++a[0]]=x;
        }
    }
}

压位读出

const int mod=10000000000000000;
char s[6666];
int a[6666];
 
int main(){
 
    printf("%d",a[a[0]]);
    for(i=a[0];i>0;i--){
        t=a[i],l=0;
        while(t>0){
            t/=10;l++;
        }
        for(j=1;j<=15-l;j++) printf("0");
        printf("%d",a[i]);
    }
}
 


  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值