【入门】高精度算法

高精度

目录

高精度

1.什么是高精度

2.高精度的作用

3.高精度读入处理数据

4.高精度比较大小

5.高精度处理进位与借位

 6.高精度加法

 7.高精度减法

8.高精度乘法

1.高精度乘以单精度

2.高精度乘以高精度

9.高精度除法

1.高精度除以单精度

2.高精度除以高精度

10.高精度开平方

11.高精度开n次方

12.高精度小技巧——压位

1.高精度压位读入处理

2.高精度压位输出处理 


1.什么是高精度

高精度算法,属于处理大数字的数学计算方法。在一般的科学计算中,会经常算到小数点后几百位或者更多,当然也可能是几千亿几百亿的大数字。一般这类数字我们统称为高精度数,高精度算法是用计算机对于超大数据的一种模拟加,减,乘,除,乘方,阶乘,开方等运算。对于非常庞大的数字无法在计算机中正常存储,于是,我们可以将这个数字拆开,拆成一位一位的,或者是几位几位的存储到一个数组中, 用一个数组去表示一个数字,这样这个数字就被称为是高精度数。高精度算法就是能处理高精度数各种运算的算法,但又因其特殊性,故从普通数的算法中分离,自成一家。

对于这类问题,不要指望long double这些东西了,基本数据类型不可能存的下。我们可以把这两个数当成字符串输入到数组中,然后模拟手动的竖式运算(不会的话,回去上小学)得出结果。

说白了,高精度计算就是解决long long也解决不了的问题。

 

2.高精度的作用

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

 

3.高精度读入处理数据

当一个数据很大的时候,我们用一个整数类型是存不下的,所以我们可以先用一个字符串输入,这样就可以输入很长的数,然后再利用字符串函数和操作运算,将每一位数取出,存入一个数组里,我们用数组里的每一位表示这个数的每一个数位。

例如:998244353用数组储存下来,a{3,5,3,4,4,2,8,9,9},一般是倒着存(从低位到高位,因为整数没有除个位以下的数位,但你的最高位还可以进位,那么你就又要开一个位置来存这个新的最高位)。

高精度读入Code

char s[6666];
int a[6666];

int main(){
    scanf("%s",s+1);//用字符串读入
    len=strlen(s+1);//这个数的长度为len
    for(int i=1;i<=len;i++){
        a[i]=s[len-i+1]-'0';//倒叙储存,每一位存一个数
    }
    return 0;
}

高精度输出Code 

int a[6666]

void write(int a[]){
    for(int i=lena;i>0;i--){
        printf("%d",a[i]);//一位一位输出这个数
    }
}

4.高精度比较大小

处理完数据之后,假设我们要比较两个数那个大怎么办呢?

我们先模拟比较1314520和1314530,首先我们看两个数的长度(即len1和len2)如果哪个数的len更长一点,那么这个数肯定要比另一个数大,否则我们才继续比较下去,这里两个数的长度是一样的,所以接下来我们就看这两个数的最高位(即1和1),相等,则继续比较下一位(3和3),也一样,继续比较下一位......直到比到十位的时候(2和3),因为2<3,所以第一个数<第二个数,直接退出。

所以,高精度比较大小的步骤大致如下:

1、比较两个数的长度,长度更长的数越大。

2、如果两个数长度相等,那么就从高位到低位一位一位比较,如果某一位数字不同时,较大的数大。否则继续比较下一位。

3、如果比到最后都没有比出谁大谁小,就说明这两个数一样大。

高精度比较大小Code

//比较a和b的大小,如果a>b则返回真,否则返回假
int a[6666],b[6666];

int compare(){
    if(lena>lenb) return 1;//lena表示a这个数的长度,lenb则表示b的长度
    if(lenb>lena) return 0;//步骤1
    for(int i=lena;i>0;i--){//从高位到底位一位一位比较
        if(a[i]>b[i]) return 1;
        if(b[i]>a[i]) return 0;
    }//步骤2
    return 0;//步骤3,a=b,即a不大于b
}

5.高精度处理进位与借位

一、那么我们怎么处理进位呢?

其实也很简单,我们再来模拟一下(模拟大法好,不会也得会),1439+887的时候,首先我们最低位相加,得到16,那么答案最低位就是6,再进个1,然后两数的十位相加,3+8=11,然后再加上进位的1,就是11+1=12,所以答案十位就是2,再进1,4+8+1=13,答案百位3,进1,1+0+1=2,答案千位2。所以结果就是6232!哦,不对反了,是2326,呵呵,这里要注意一下,输出的时候是倒着输出的,千万不要忘了。

总结一下,进位的步骤大致如下:

1、将当前位置的数相加,当前位的结果就是当前结果除以10的余数。

2、再讲当前结果除以10加到高位,表示进位。

注意:有同学可能会有疑问,为什么一定要倒着储存这个数呢?顺着存不是更好吗?这里我举一个非常简单的例子,比如10+90,谁都知道答案是100,那么我们来看看顺着储存和倒着储存有什么区别:

1.顺着存:a={1,0},b={9,0},当a的最高位(即数组第1位)加上b的最高位(即数组第2位)时我们是不是得进位?!?但是a的最高位是数组的第一位,那么我们要进位到a数组的第几个位置呢?第0个位置?太复杂了。还是存在第一个位置并把所有的数往数组右边移动一位?那更不行,时间复杂度又太高,所以不好办。

2.倒着存:a={0,1},b={0,9},当a的最高位(即数组第2位)加上b的最高位(即数组第2位)时我们同样要进位,这个时候就好办了,直接进位到数组的第三位就好了,所以答案的数组就是{0,0,1},倒过来输出答案100。

高精度进位Code

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];
        c[i+1]=c[i]/10;
        c[i]=c[i]%10;
    }
    while(c[lenc+1]>0) lenc++;//答案的长度有时还会增加
}

 二、接下来讲讲当减法的时候如何借位

1、将当前位置的数向减。

2、如果结果大于或等于0就直接作为当前位的答案。

3、否则将结果加10作为当前位的答案,在将高位的数-1即可。

高精度借位Code

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
}

 6.高精度加法

 至此,就可以进行任意你想进行的运算了,首先我们来看看加法,其实上面的代码已经差不多写出来了。

高精度加法Code

#include
#include
#include
#include
#include
using namespace std;
int a[6666],b[6666],c[6666];
int lena,lenb,lenc;
char s1[6666],s2[6666];

int main(){
    scanf("%s %s",s1+1,s2+1);
    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=max(lena,lenb);
    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;
    }
    while(c[lenc+1]>0) lenc++;
    for(int i=lenc;i>0;i--) printf("%d",c[i]);
}

 7.高精度减法

高精度减法Code

#include
#include
#include
#include
#include
using namespace std;
int a[6666],b[6666],c[6666];
int lena,lenb,lenc;
char s1[6666],s2[6666];

int main(){
    scanf("%s %s",s1+1,s2+1);
    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--) printf("%d",c[i]);
}

8.高精度乘法

1.高精度乘以单精度

高精度乘以单精度Code

#include
#include
#include
#include
#include
using namespace std;
int a[6666],b;
int lena;
char s1[6666];

int main(){
    scanf("%s %d",s1+1,&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--) printf("%d",a[i]);
}

2.高精度乘以高精度

高精度乘以高精度Code

#include
#include
#include
#include
#include
using namespace std;
int a[6666],b[6666],c[6666];
int lena,lenb,lenc;
char s1[6666],s2[6666];

int main(){
    scanf("%s %s",s1+1,s2+1);
    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--) printf("%d",c[i]);
}

9.高精度除法

1.高精度除以单精度

手动模拟一下,我们只要记录一个r,表示当前的余数,然后不断除就可以了。

高精度除以单精度Code

#include
#include
#include
#include
#include
using namespace std;
int a[6666],b,r;
int lena;
char s1[6666];

int main(){
    scanf("%s %d",s1+1,&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--) printf("%d",a[i]);
}

2.高精度除以高精度

很多人都不知道高精度如何整除以一个高精度,那么这里我就讲一下我的方法吧,可能不是最优的。

我们知道除法是乘法的逆运算,那么我们为什么不可以看做是我们现在要求一个数乘以除数等于被除数呢?那么枚举肯定是不行的,这个时候我们就要用到二分啦(二分大发好啊~),没错高精度二分商,实际上就是一个高精度加法(mid=l+r),然后高精度除以单精度(mid/2),最后再高精度减法(r=mid-1)就可以实现二分了,我们二分出来的数直接高精度乘以高精度判断一下就可以了(代码中数组中的第0个位置表示此数的长度,瞬间暴露PC党......)。

高精度除以高精度Code

#include
#include
#include
#include
#include
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]0;i--){
        if(a[i]>b[i]) return 0;
        if(a[i]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(){
    scanf("%s %s",s1+1,s2+1);
    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--) printf("%d",ans[i]);
    return 0;
}

码打的有点丑,不要介意......

 

10.高精度开平方

高精度开平方其实也还算比较简单,有一次考试就有一道原题,我一怒之下把它给切了。

没错,还是二分。二分答案。利用高精度加法和高精度除以单精度可以实现二分的效果,然后直接高精度乘法乘起来再高精度比较一下大小,再用高精度减法移动一下l和r就可以了。其实这也算是高精度比较综合的做法了。码量虽然惊人,但其实高精度除以高精度改一改就好了

高精度开平方Code

#include
#include
#include
#include
#include
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]0;i--){
        if(a[i]>b[i]) return 0;
        if(a[i]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(){
    scanf("%s",s1+1);
    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;
}

 

11.高精度开n次方

稍加处理即可:开平方时将二分的答案乘两次,那么开n次方不就是将二分出来的答案乘n次吗?

高精度开n次方Code

#include
#include
#include
#include
#include
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]0;i--){
        if(a[i]>b[i]) return 0;
        if(a[i]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(){
    scanf("%d\n",&n);
    scanf("%s",s1+1);
    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;
}

12.高精度小技巧——压位

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

1.高精度压位读入处理

高精度压位读入处理Code

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;
        }
    }
}

2.高精度压位输出处理 

高精度压位输出处理Code

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");
        if(a[i]) printf("%d",a[i]);//注意这里如果a[i]为0就会多输出一个0,所以要特判
    }
}

 

更多的题目实现需要用已有的知识灵活变通。 
以上为高精度算法大部分内容。希望你能有所收获。

 


作者:zsjzliziyang 
QQ:1634151125 
转载及修改请注明 
本文地址:https://blog.csdn.net/zsjzliziyang/article/details/82050337

你可能感兴趣的:(算法,高精度)