首页 > 其他 > 详细

高精度

时间:2019-08-21 14:59:53      阅读:151      评论:0      收藏:0      [点我收藏+]
【转】https://www.cnblogs.com/yueansixing/articles/9093475.html
什么是高精度数?
在一般的科学计算中,会经常算到小数点后几百位或者更多,当然也可能是几千亿几百亿的大数字。一般这类数字我们统称为高精度数,高精度算法是用计算机
对于超大数据的一种模拟加,减,乘,除,乘方,阶乘,开放等运算。 对于一个很大的数字N >= 10^ 100, 很显然这样的数字无法在计算机中正常存储。于是, 我们想到了办法,将这个数字拆开,拆成一位一位的 或者是四位四位
的存储到一个数组中, 用一个数组去表示一个数字。这样这个数字就被称谓是高精度数。 对于高精度数,也要像平常数一样做加减乘除以及乘方的运算,于是就有了高精度算法: 由于计算机输入计算结果的精度通常受到计算机的限制,如:在双精度方式下,计算机最多只能输出16位有效数字,如果超过16位,则只能按浮点形式输出,
另外,一般计算机实数表示的范围为1038,如果超过这个范围,计算机就无法表示了。但是我们可以通过一些简单的办法来解决这个问题。这就是我们要说
的高精度计算机。

一、高精度数的输入和存储方法

输入

单个高精度数字可能长达200位,可以通过两种方式读入:1. 单字符读取、判断并存储,直到遇到“\n”(换行符),可以使用scanf("%c", &c),或c=getchar()这种方法麻烦;2. 将数字作为字符串读入,存入字符数组中,这种方法最简单、高效:scanf("%s", temp);

存储

字符到数字:使用字符数组存储容易,但计算比较麻烦,因数字在字符数组中是以字符0~9的形式存在(ASCII码范围:48~57),将所有数位以数字0~9的形式转存在一个int数组中会方便运算。N==‘N‘-‘0‘可以实现字符N到数字N的转换。

反序存放:使用竖式做加法运算时,规则是最低位和最低位对齐,在int数组中如果将数字正序存放,对应计算位的下标就不一样,不便于计算,因此我们将数字反序存放在数组中,就可以使得低位和低位一一对应(下标相同),方便运算,如12345+9987:

//array index   1 2 3 4 5 6 7 8 9
                5 4 3 2 1
            +   7 8 9 9
            -------------
                2 3 3 2 2
//jw            1 1 1 1

//array index是数组下标,jw代表进位

存储位数: 上面的例子并没有使用数组的0号单元,这主要是为了方便运算的缘故,例如n位数字,我们从1循环至n即可,这样思路更加清晰。而留下的0号单元刚好可以用来存储数组的长度,长度Array[0]=strlen(temp);

代码示例

技术分享图片
void getNum(int num[]){             //数组初始化和高精度数字构建 
    char temp[500];
    scanf("%s", temp);              //数字作为字符串读入
    memset(num, 0, sizeof(int)*500);//将数组的所有字节置0  
    num[0]=strlen(temp);            //将位数存于num[0]中 
    for(int i=1; i<=num[0]; i++)    //逆序将每一位存储于num中
        num[i]=temp[num[0]-i]-0;
    return;
}

//使用方法
int main(){
    int num1[500];
    int num2[500];
    getNum(num1);
    getNum(num2);
    ......
    return 0;
}
技术分享图片

 

上面的代码中用到了memset(),该函数位于cstring头文件中,用于内存块的赋值,在例子中起到了初始化数组的作用
参数1是内存块的起始地址, num是数组的名字,即起始地址
参数2是初始化值,范围是0~255,因memset是按字节为单位进行的
参数3是初始化的字节数,sizeof(int)计算出int的字节数,乘以数组大小500

这里若是没有用memset进行内存块初始化会导致运算出错:如果参与计算的数字A和数字B位数不同,高位相加时,小数字的高位空间的值具有不确定性,如31415+12:

//array index       1 2 3 4 5 6 7 8 9 10 ...
                A   5 1 4 1 3
                B   2 1 ? ? ?
                ------------------------
                C   7 2 ? ? ?
//jw                0 0 ? ? ?

可见,数组在使用前全部置0可以避免这个问题,你也可以用循环来解决,当然使用memset更加方便。


二、高精度加法

思路

按位相加:从两个数A、B的低位开始按位相加,一边加一边进位,C[i]=A[i]+B[i]+jw,进位是上一次相加产生的。

代码示例

技术分享图片
void jiafa(int A[], int B[], int C[]){        //C=A+B
    int i, jw;                              //i从1循环至max(A[0], B[0])
    for(i=1, jw=0; i<=A[0] || i<=B[0]; i++){
        C[i]=A[i]+B[i]+jw;                  //按位相加 
        jw=C[i]/10;                         //求进位 
        C[i]%=10;                           //与10求余
    }
    C[i]=jw;                                //存放最后一个进位
    C[0]=C[i] ? i : i-1;                    //确定C的位数
    return;
}
//使用方法
int main(){
    int num1[500];
    int num2[500];
    int result[500];
    getNum(num1);
    getNum(num2);
    memset(result, 0, sizeof(result));      //在当前函数中才可以如此计算大小
    jiafa(num1, num2, result);
    printNum(result);                       //后面提供函数代码
    return 0;
}
技术分享图片

三、高精度减法

思路

大小判断:相减之前要判断两个数A、B的大小关系,如果A>B,则计算A-B,否则计算B-A,需要编程实现大小判断。

按位相减:从两个数A、B的低位开始按位相减,如果L[i]<R[i],就需要向高位借位,因为我们已经确定了左数字L大于右数字R,一定能借到。 if(L[i]<R[i]){ C[i]=L[i]+10-R[i]; L[i+1]--;}

确定有效位:按位减完后,高位有可能变为0,如1000-990后等于10,这时就要重新计算确定结果的有效位数C[0]是多少。可以从位置max(L[0], R[0]开始倒推,直至遇到非0值停止,这时的位置就是有效位数。

代码示例

技术分享图片
bool compare(int L[], int R[]){             //实现两个高精度数字的大小比较 
    if(L[0]>R[0])       return true;        //位数高的大
    if(L[0]<R[0])       return false;       //位数低的小
    int i;
    for(i=L[0]; i>=1; i--){                 //位数相同逐位比较
        if(L[i]>R[i])   return true;        //数字大的大
        if(L[i]<R[i])   return false;       //数字小的小
    }
    return true;                            //能执行到这里说明相等,返回true
}

void jianfa(int L[], int R[], int C[]){     //C=L-R
    for(int i=1; i<=L[0]; i++)
        C[i]=L[i];                          //把L的所有数位复制到C
    C[0]=L[0];                              //C的位数暂等于L的位数
    int i;
    for(i=1; i<=R[0]; i++){                 //右值有多少位,就减多少次
        if(C[i]<R[i]){
            C[i+1]--;                       //向高位借1 
            C[i]+=10;                       //当前位+10 
        }
        C[i]-=B[i];                         //按位减 
    }
    if(i<C[0])  return;                     //计算有效位数:L[0]-R[0]>=2
    else{                                   //计算有效位数:L[0]-R[0]==1 or 0 
        while(C[i]==0 && i>=1)  i--;
        A[0]=(i==0) ? 1 : i;                //例如10000-9999
    }
    return;
}
//使用方法
int main(){
    int num1[500];
    int num2[500];
    int result[500];
    getNum(num1);
    getNum(num2);
    memset(result, 0, sizeof(result));      
    if(compare(num1, num2))     jianfa(num1, num2, result);
    else                        jianfa(num2, num1, result);
    printNum(result);
    return 0;
}
技术分享图片

四、高精度乘法

思路

类似加法,可以用竖式求乘法,同样也有进位,对每一位进行乘法运算时,必须进行错位相加:如856×25,见如下规律:

//array index       1  2  3  4  5  6  7  8  9  ...
                A   6  5  8
                B   5  2
                ----------------------------------
                    0  8  2  4
                       2  1  7  1
                ----------------------------------
                    0  0  4  1  2
                    
//array index       1  2  3  4  5  6  7  8  9  ...
                A   a1 a2 a3 a4
                B   b1 b2
                ----------------------------------
                C   c1 c2 c3 c4
                       c2 c3 c4 c5
//本例中未写出进位

综上分析,乘法要进行B[0]轮,每轮要进行A[0]次乘法和进位运算,这就找到了循环的规律。结合错位相加,可知C[i+j-1]=C[i+j-1]+A[i]*B[i]+jw,进位jw=c[i+j-1]/10

有效位数:计算完成后,需要确定结果的有效位数,使用和减法类似的方法从可能的最大位数往后推,直到遇到非0的数位,当前位置就是有效位数。对于M位的数字A和N位的数字B向乘,最大位数为M+N

代码示例

void chengfa(int A[], int B[], int C[]){        //C=A*B
    int i,j;
    for(i=1; i<=B[0]; i++){                     //B[0]轮乘法
        int jw;                                 //进位 
        for(j=1, jw=0; j<=A[0]; j++){           //每轮按位乘A[0]次
            C[i+j-1]=C[i+j-1]+A[j]*B[i]+jw;     //错位相加
            jw=C[i+j-1]/10;                     //求进位
            C[i+j-1]%=10;                       //进位后,与10求余
        }
        C[i+A[0]]=jw;                           //存储最后一个进位
    }
    int len=A[0]+B[0];                          //最大可能位数 
    while(C[len]==0 && len>1)   len--;          //确定有效位数
    C[0]=len;
    return;
}
//使用方法
int main(){
    ...      
    chengfa(num1, num2, result);
    printNum(result);
    return 0;
}
五、高精度除低精度

思路

高精度初低精度采用竖式思想,由于除数是低精度,最后的商可能是高精度,余数一定为低精度。

商有效位数:M位的数字A除以N位的数字B,商的位数最大为M-N+1,因为B是低精度数字,计算长度N比较麻烦,简单起见,也可以认为商的最大位数为M, 从最大数位

往后推,直到遇到非0的数位,当前位置就是有效位数。

以下是一个模拟计算过程的例子,113056/23:

//
被除数数位      当前位数字  过程被除数      过程除结果
i=6             1           1/23            商:0
i=5             1           11/23           商:0
i=4             3           113/23          商:4,余:21
i=3             0           210/23          商:9,余:3
i=2             5           35/23           商:1,余:12
i=1             6           126/23          商:5,余:11
//计算结果      商:4915, 余数:11
代码示例:
void chuDi(int A[], int B, int C[], int &yushu){    //A/B=C 余数:yushu
    int i, t;                                       //t为过程被除数
    for(i=A[0], t=0; i>=1; i--){                    //从被除数高位循环到低位
        t=t*10+A[i];                                //更新t
        C[i]=t/B;                                   //计算C[i]
        t%=B;                                       //更新t
    }
    yushu=t;                                        //计算完后t就是余数
    i=A[0];                                         //计算C有效位数
    while(C[i]==0 && i>1) i--;
    C[0]=i;
    return;
}
//使用方法
int main(){
    int num1[500];
    int num2;
    int shang[500];
    int yushu;
    getNum(num1);
    scanf("%d", num2);
    chuDi(num1, num2, shang, yushu);
    printNum(shang);
    count<<" "<<yushu;
    return 0;
}

六、高精度除高精度

 

思路

高精度除高精度是这几种运算中最难的一种。仍然可以模仿竖式的方式进行,过程有点儿麻烦。另一种办法是利用减法:被除数A-除数B,看看能减多少个,直到A<B,这时剪去的个数就是商,剩下的A就是余数。如果利用前面编写好的高精度减法jianfa()compare()的话,实现起来岂不是很容易?可惜不是这样,假设被除数A的位数M与除数B位数N之差M-N很大,这个范围有可能从0~200,按照最大的情况200考虑,这意味着商值也是高精度数字,而你要减10^200次才能减完,这是一个天文数字。

所以,明白否?解决这个问题也很简单,我们不一个一个减,而是按照最大可能的数量级减,例如:12345/45:

//商的最大位数i=M-N+1,即4,设计一个 临时减数,减数后面补齐i-1个0,再进行减法
i=4     12345 < 45000   可以减0个   shang[4]=0      减后A:12345
i=3     12345 < 4500    可以减2个   shang[3]=2      减后A:3345
i=2     3345  < 450     可以减7个   shang[2]=7      减后A:195
i=1     195   < 45      可以减4个   shang[1]=4      减后A:15

//因shang[4]=0,故商的有效位数shang[0]--,为3
//结果  商为274,余数15

看明白了吗,这样的计算过程,仅仅减了2+7+4=13次,而非274次,可见一个一个减是绝对不靠谱的。下面提供了代码,注意:减法函数jianfa()被修改了,使得直接把结果存入A中,而不需要存在另一个数组C中,这是为了简化后面运算的缘故。

代码示例

void jianfa(int A[], int B[]){              //修改了的减法,使得直接在A数组上减
    int i;                                  //不再需要存在数组C,简化后面的运算
    for(i=1; i<=B[0]; i++){
        if(A[i]<B[i]){
            A[i+1]--;                       //向高位借1 
            A[i]+=10;                       //当前位+10 
        }
        A[i]-=B[i];                         //按位减 
    }
    if(i<A[0])  return;                     //计算有效位数
    else{                                
        while(A[i]==0 && i>=1)  i--;
        A[0]=(i==0)? 1 : i;
    }
    return;
}
                                            //shang=A/B, yushu为余数
bool chuGao(int A[], int B[], int shang[], int yushu[]){
    memset(shang, 0, sizeof(int)*300);      //初始化shang
    memset(yushu, 0, sizeof(int)*300);      //初始化yushu
    shang[0]=A[0]-B[0]+1;                   //商的最大数位 
    for(int i=shang[0]; i>=1; i--){         //从高位到低位开始计算商
        int jianshu[300];                   //构造要减的减数
        memset(jianshu, 0, sizeof(jianshu));
                                            //这个函数下面有解释
        memcpy(jianshu+i, B+1, sizeof(int)*B[0]);
        jianshu[0]=B[0]+i-1;                //确定减数的位数
        while(compare(A, jianshu)){         //通过循环减
            jian(A, jianshu);                     
            shang[i]++;                     //减去一个商的对应为+1
        }
    }
    if(shang[shang[0]]==0)  shang[0]--;     //判断商的最高位是否有效
    memcpy(yushu, A, sizeof(int)*300);      //A就是余数,把它完全复制给yushu
}

//使用方法
int main(){
    int num1[300];                          //数组A存储第1个数字信息 
    int num2[300];                          //数组B存储第2个数字信息
    int shang[300];
    int yushu[300];
    init(num1);
    init(num2);
    chuGao(num1, num2, shang, yushu);
    print(shang);
    printf(" ");
    print(yushu);
    return 0;
}

上面的代码中用到了memcpy(),该函数位于cstring头文件中,用于内存块之间的复制
参数1是destination,即复制的目的地,为一地址
参数2是source,即复制数据的来源,为一地址
参数3是要复制的字节数,sizeof(int)计算出int的字节数,乘以相应的元素个数N

例程中的memcpy(jianshu+i, B+1, sizeof(int)*B[0])是从B数组的1号位置开始(跳过B[0]),复制B[0]个数位,目的地为jianshu数组的第jianshu+1+i-1个位置,+1是为了跳过jianshu[0]+i-1是把这几个位置空成0,以构造最大数量级的减数。晕了么有?别怕,其实可以把代码写的长一些,但是更简单一些的,这里是偷懒了;-)

高精度

原文:https://www.cnblogs.com/450go/p/11374120.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!