高精度全家福

众所周知,int型的数据范围是-2147483648~+2147483647(32位存储,有1位存符号),无符号行unsigned   int : 0~4294967295 ,long long的范围也不过-9223372036854775808~9223372036854775807  (19位数,64位存储,有1位存符号 ),无符号型unsigned long long:0~18446744073709551615(20位数)那么问题来了,如果要求计算超过20位数的数的四则运算怎么办?这里就用到高精度算法了。

高精度算法的核心思想就是用数组模拟竖式运算。首先用字符串读数,并倒着把每位数存到数组里(方便进位,想想如果正着存到数组里时最高位进位的处理可没倒着存方便啊)(注意正负号要提出,用于判断结果正负、减法顺序和对负数运算的转换)。

核心代码:

1     scanf("%s", str);
2     int len=strlen(str);
3     for(int i=len-1;i>=0;i--)a[len-i]=str[i]-'0'; //不要忘了把“字符数”转化为“数字数”

 

(为方便讨论,下面情况均为正数之间的运算。若有负数,提出负号来,并转换成相应的正数运算并把结果相应取负)

1.高精度加法:最简单的高精度运算,只需注意进位就可。从最低位开始相加,每位上的数若超过十就进下一位同时把当前位模10;

核心代码:

1     for( i=1;i<=strlen(s1)||i<=strlen(s2);i++){
2         c[i]=a[i]+b[i];
3                if(c[i]>=10){ c[i+1]++;   c[i]%=10;} 
4     }
5       if(c[i+1]>0) i++; 
6        while(c[i]==0&&i>0) i--;

2.高精度减法:也很简单的高精度运算。先判断两数大小,让大的减小的,若一开始的被减数小于减数,则在结果前补上负号。从最低位减起,注意借位即可。

核心代码:

for(i=1;i<=strlen(s1);i++)
    {
        c[i]=a[i]-b[i];
        if(c[i]<0){
            c[i]+=10;
            c[i+1]--;
        }
    }
    while(c[i]==0&&i>0)i--;

3.高精度乘法:只是一开始的规律相对于前两个高精度运算来说较难理解罢了。回忆小学乘法竖式的学习(\滑稽),我们算第一个数第i位乘第二个数第j位的结果写到竖式分割线下面第i+j-1的位置上并进位。

核心代码:

 1 n=strlen(s1),m=strlen(s2);
 2     for(int i=1;i<=n;i++)
 3         for(int j=1;j<=m;j++)
 4             c[i+j-1] += a[i]*b[j];
 5 
 6     for(int i=1;i<=n+m-1;i++){
 7         c[i+1]+=c[i]/10;
 8         c[i]%=10;
 9     }
10     n=n+m-1;
11     while(c[n+1]>0)n+=1;

  

 

4、高精度除法:高精度劝退知识点,众多老师口中的“不要求”……好吧,言归正传,分为大数除以小数和大数除以大数的情况:

 (1)大数除以小数(小数在数据类型范围内):

    从被除数最高位开始试除除数,商写到竖式横线上面的对应位,留下的余数乘10再加上下位并继续试除直到最后一位。、

 核心代码:

1   n=strlen(s1);
2     for(int i=n;i>0;i--){
3         c[i]=a[i]/B;
4         a[i-1]+=(a[i]%B)*10;
5     }
6     while(c[n]==0 && n>0)n--;

  (2)大数除以大数(两个大数组相爱相杀的那些事儿~)

    从被除数的第(被除数的位数-除数的位数)位开始,将被除数数前面的数组及当前位单独与除数相减至差小于除数,减法的次数即在写在此位的商(减法模拟除法),在把剩下的

数与下一位继续减除数算下一位。。。直到把最后一位算完。

  核心代码(好像80%都是核心):

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 using namespace std;
 5 int a[301],b[301],c[301];
 6 void init(int a[])
 7 {
 8     char s[302];
 9     cin>>s;
10     a[0]=strlen(s);//位数 
11     for(int i=1;i<=a[0];++i)
12         a[i]=s[a[0]-i]-'0';
13 }
14 void fuyin(int p[],int q[],int det)
15 {
16     for(int i=1;i<=p[0];++i) q[det+i-1]=p[i]; 
17     q[0]=det+p[0]-1;
18 }
19 int compare(int a[],int b[])
20 {
21     if(a[0]>b[0]) return 1;
22     if(a[0]<b[0]) return -1;
23     for(int i=a[0];i>=1;--i)
24     {
25         if(a[i]>b[i]) return 1;
26         if(a[i]<b[i]) return -1;
27     }
28     return 0;
29 }
30 void jian(int a[],int b[])
31 {
32     if(compare(a,b)==0){a[0]=0;return;}
33     for(int i=1;i<=a[0];++i)
34     {
35         if(a[i]<b[i]) 
36         {
37             a[i+1]--;
38             a[i]+=10;
39         }
40         a[i]-=b[i];
41     }
42     while(a[a[0]]==0&&a[0]>0) a[0]--;
43 }
44 void chuhao(int a[],int b[],int c[])
45 {
46     int tep[302];
47     c[0]=a[0]-b[0]+1;
48     for(int i=c[0];i>=1;--i)
49     {
50         memset(tep,0,sizeof(tep));
51         fuyin(b,tep,i);                //把除数拷贝到与被除数当前位一样“高”的地方 
52         while(compare(a,tep)>=0) {c[i]++;jian(a,tep);} //当前位及以上单独减除数并记录减法次数(商)直到减不起 。 
53     }
54     while(c[c[0]]==0&&c[0]>0) c[0]--;
55 }
56 void print(int a[])
57 {
58     if(a[0]==0) cout<<0;
59     else
60     {
61         for(int i=a[0];i>=1;--i)
62         cout<<a[i];
63     }
64     cout<<endl;
65 }
66 int main()
67 {
68     init(a);//被除数 
69     init(b);//除数 
70     chuhao(a,b,c);
71     print(c);//输出商 
72     print(a);//输出被除数被除数各种减完剩下的——余数 
73     return 0;
74 }

 5、高精度取模

  模数一般情况下为类型数据范围内的小数。与除法类似,但不用记录商,在除法的基础上稍微修改下代码就是了。

总的来说高精度就是对小学竖式的模拟,只要小学学好了,打高精度岂不手到擒来?

 

优化/技巧:

  压位:让一位的权值从10调整为更大的数,使每位能存的数更大,使得在数的位数一样时用到的数组元素个数更少,也能使常数更小。

    权值一般取10的整数幂,方便输出。若不要求输出或最后结果能用小类型表示出来,可不拘泥(虽然在一般情况下也不必10的幂优多少)。只涉及加减除模,保证权值*2不溢出就好;涉及乘法则要保证权值立方不溢出。

    输出要注意最高位无前导零,中间位有前导零。输出前导零,有简单代码(以权值位1e3为例):printf("%0

      谈谈高精度压位的坑

 

    

 

posted @ 2019-04-09 21:34  千叶繁华  阅读(215)  评论(0编辑  收藏  举报