数据结构学习(一):高精度算法


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

由于计算机运算是有模运算,数据范围的表示有一定限制,如整型int(C++中int 与long相同)表达范围是(-231~231-1),unsigned long(无符号整数)是(02^32-1),都约为几十亿.如果采用实数型,则能保存最大的double只能提供1516位的有效数字,即只能精确表达数百万亿的数.因此,在计算位数超过十几位的数时,不能采用现有类型,只能自己编程计算。 —— [ 百度百科 ]

题目:

  1. 整数长度在一百位以上*
  2. 实现两长整数的加减乘除操作,除法要返回商和余数*
  3. 输入输出均在文件中*

结构设计

高精度计算时一般用一个数组来存储一个数,数组的一个元素对应于数的一位(当然,在以后的优化中为了加快计算速度,也可用数组的一个元素表示数的多位数字),表示时,由于数计算时可能要进位,因此为了方便,将数由低位到高位依次存在数组下标对应由低到高位置上。

运算因子超出了整型、实型能表示的范围,肯定不能直接用一个数的形式来表示。在c++中,能表示多个数的数据类型有两种:数组和字符串

数组:每个数组元素存储1位(在优化时,这里是一个重点!),有多少位就需要多少个数组元素;用数组表示数的优点:每一位都是数的形式,可以直接加减;运算时非常方便。用数组表示数的缺点:数组不能直接输入;输入时每两位数之间必须有分隔符,不符合数值的输入习惯。

字符串:String型字符串的最大长度是255,可以表示255位。字符串长度不受限制。用字符串表
示数字的优点:能直接输入输出,输入时,每两位数之间不必分隔符,符合数值的输入习惯;用字符串表示数的缺点:字符串中的每一位是一个字符,不能直接进行运算,必须先将它转化为数值再进行运算;运算时非常不方便。

综合以上所述,对上面两种数据结构取长补短:用字符串读入数据,用数组存储数据。

 n = strlen(numberN);m = strlen(numberM);
 a=new int[n];b=new int[m];              //两个和数据等长的中间数组 
 int i, j;
 for (i = 0, j = n - 1; i < n; i++, j--) {//将字符数组转换为整型数数组 
	 a[i] = numberN[j] - '0';
 }

高精度计算分为四块,分别是加、减、乘、除,每一种运算都有其单独的方法,我们在本篇文章里将会由简至难分别简单说一下使用数组时,四种运算的思想和方法。文章末还会稍稍提一下使用双向循环链表时的程序思路。

加运算:在加法运算中采用的方法就是将两个加数和被加数数组从第一位开始对其,并从第一位开始向后遍历,每一位相加并判断进位,直至到达输入数据末端为止。
运算过程:
以竖式计算35+86为例。
需要注意的问题是:

  1. 运算顺序:两个数靠右对齐;从低位向高位运算;先计算低位再计算高位;
  2. 运算规则:同一位的两个数相加再加上从低位来的进位,成为该位的和;这个和去掉向高位的进位就成为该位的值;如上例:3+8+1=12,向前一位进1,本位的值是2;
  3. 最后一位的进位:如果完成两个数的相加后,进位值不为0,则应添加一位;
  4. 如果两个加数位数不一样多,则按位数多的一个进行计算;
        for(i=0;i<3000;i++)r[i]=0;
		    if(n>=m)
		{
			for(i=0;i<m;i++)
		    r[i]=a[i]+b[i];
		    
		    for(i=m;i<n;i++){
		    	r[i]+=a[i];
			}
	    }
	        else
	    {
	    	for(i=0;i<n;i++)
		    r[i]=a[i]+b[i];
		    for(i=n;i<m;i++)
			r[i]+=b[i]; 
		}

结果的输出(这也是优化的一个重点):
按运算结果的实际位数输出,可以采取从数组末端向前遍历,直到找到一位不为0的数字为止的方法来实现多余0的排除,做到实际位数输出。

for ( j = 2999; j > 0; j--) {  //从后向前遍历,当最后一位不为零时,即为到达运算结果的最后一位(原数组全为0) 
			if (r[j] != 0)
				break;
		}

减运算:和高精度加法相比,减法在差为负数时处理的细节更多一点:当被减数小于减数时,差为负数,差的绝对值是减数减去被减数;在计算时,将数据分成两种情况,当被减数数组大于减数数组时,使用被减数数组直接减去减数数组,同时判断是否需要借位;当被减数数组小于减数数组时,在减数数组大于被减数数组部分,将减数数组每位的负数存入结果数组。

    for(i=0;i<3000;i++)r[i]=0;
	if(n>=m)                          //做比较,分被减数数组大于减数数组和被减数数组小于减数数组两种情况 
	{                                 //被减数数组大于减数数组 
		for(i=0;i<m;i++)
		  r[i]=a[i]-b[i];
		for(i=m;i<n;i++)r[i]+=a[i];
	}
	else if(n<m)                     //被减数数组小于减数数组 
	{
	    for(i=0;i<n;i++)
		r[i]=a[i]-b[i];
		for(i=n;i<m;i++)r[i]-=b[i]; 
    }

算法流程:
(1).读入被减数a,b(字符串);
(2).置符号位:判断被减数是否大于减数:分两种情况进行两种不同的运算
(3).被减数与减数处理成数值,放在数组中;
(4).运算:
A、取数;
B、判断是否需要借位;
C、减,将运算结果放到差数组相应位中;

在这里有个细节问题:
▲如何判断被减数与减数的大小?
答:如果位数一样,直接比较字符串大小;否则,位数多的大。

处理进位与退位: 跟加法一样,在for语句外面先将退位清零,用被减数再减去退位。
注意,由于每一个数位不一定都得向前一位借位,所以这里退位得清零。例如,234-25,个位需借位,而十位不用} 接着,再判断,当被减数某一位不够减时,则需加上前一位退位过来的数。前一位退一位,就等于后一位加上10。

		for (i = 0; i < j; i++) {  //进位的处理 
		    if (r[i] < 0) {
				r[i + 1] - 1;
				r[i] += 10;         
			}
		}

乘法:乘法的思想与加法基本相同,两个数组相对应位置的数字相乘并考虑进位,直至数组内所有有效数组都参与了运算。

运算流程如下:
(1) 读入被乘数a,乘数b
(2) 转成数值存在数组a,b中;记下a,b的长度length1,length2;
(3) i赋为b中的最低位;
(4) 从b中取出第i位与a相乘,累加到另一数组c中;
(5) 打印结果

for (i = 0; i < n; i++) {
			for (j = 0; j < m; j++) {
				r[i + j] += a[i] * b[j];    //根据乘法性质来进行运算 
			}
		}
		for (i = 0; i < n + m; i++) {  //进位的处理 
			if (r[i] >= 10) {
				r[i + 1] += r[i] / 10;
				r[i] %= 10;         
			}
		}

*乘法的运算需要注意的是结果数组的长度,因为乘法运算的性质,决定了乘法运算的结果会很大,最大能够达到乘数与被乘数数组的两倍,所以在定义结果数组时,长度必须足够,才不会发生数据溢出情况。

除法:在除法运算中,使用到了很多与之前不同的计算思想,首先为除法单独写了一个减法方法SubStract(因为前面的减法因为参数或其他一些原因无法直接运用到除法中),在SubStract方法中,对除数和被除数数组a b进行了大小比较,同时做减法,与前面的减法最大的不同之处就在于,多了一步查找结果最高位:

int AdvancedOperation<T>::SubStract(int a[], int b[], int n, int m )
{
	int i;  
    if( n < m )  
        return -1;  
    if( n == m )  
    {                        //判断a > b  
        for( i=n-1; i>=0; i-- )  
        {  
            if( a[i] > b[i] )   //若大,则满足条件,可做减法  
                break;  
            else if( a[i] < b[i] ) //否则返回-1  
                return -1;  
        }  
    }  
    for( i=0; i<=n-1; i++ )  //从低位开始做减法  
    {  
        a[i] -= b[i];  
        if( a[i] < 0 )          //若a<0,则需要借位  
        {  
            a[i] += 10;         //借1当10  
            a[i+1]--;           //高位减1  
        }  
    }
    
    for( i=n-1; i>=0; i-- )       //查找结果的最高位  
        if( a[i] )               //最高位第一个不为0  
            return (i+1);       //得到位数并返回  
    
    return 0;                  //两数相等的时候返回0  
}

在除法方法中,设置了nTimes和nTemp来表示两数相差位数和SubStract函数返回结果。

除法运算是减法的另一种使用 。在除法运算中,被除数与除数是倍数加余数的关系,一个被除数除以一个除数,相当于用被除数不断减去这个除数,当倍数变为小于或等于除数时,运算结束,减运算运行的次数就是商,剩下的被除数就是除运算的余数。

        if( n < m )   //如果被除数小于除数,结果为0  
        {  
            cout<<"0"<<endl;    
        }  
        nTimes = n - m;    //相差位数  
        for ( i=n-1; i>=0; i-- )    //将除数扩大,使得除数和被除数位数相等  
        {  
            if ( i>=nTimes )  
                b[i] = b[i-nTimes];  
            else                     //低位置0  
                b[i] = 0;  
        }  
        m = n;  
        for(j=0; j<=nTimes; j++ )      //重复调用,同时记录减成功的次数,即为商  
        {  
            while((nTemp = SubStract(a,b + j,n,m - j)) >= 0)  
            {  
                n = nTemp;      //结果长度  
                r[nTimes-j]++; //每成功减一次,将商的相应位加1  
            }  
        }  

.

除了四种运算方法之外,还采用了文件形式进行数据输入输出,计算所需的两个数组都存放在TXT文件内,每次的运算结果也存到TXT文件里。具体操作为:使用fseek()和ftell()函数来得到文件内数据长度;使用fscanf()函数读取文件内数据;使用fprintf()函数来将输出数据设置好格式并写入文件内。

	//文件读取,存入数组中 
	fpRead1 = fopen("E:\\C++\\高精度算法\\data1.txt", "r");
	if (fpRead1 == NULL){  
        cout<<"Open File Failed."<<endl;  
        exit(0);  
    } 
    //得到文件内数据的长度 
    fseek(fpRead1,0,SEEK_SET);
    fseek(fpRead1,0,SEEK_END);
    long length1=ftell(fpRead1);// length1就是文件的长度
    
    fseek(fpRead1,0,SEEK_SET);     //文件流位置放到开头
	for(i=0;i<length1;i++){   
	    fscanf(fpRead1, "%s", &numberN[i]); //读取单个数据 
    } 

    fpRead2 = fopen("E:\\C++\\高精度算法\\data2.txt", "r");
    if (fpRead2 == NULL){  
        cout<<"Open File failed.\n";  
        exit(0);  
    } 

测试结果:

代码下载

这里写图片描述

以上完整代码及测试数据下载请点击这里


微信公众号

同时也欢迎各位关注我的微信公众号 南木的下午茶

在这里插入图片描述


完整代码如下:

.h

#ifndef CIRCLE_H
#define CIRCLE_H

#include <iostream>
#include <stdio.h> 
#include <stdlib.h>
#include <string.h>
#include<string.h>  
#define MaxLen 200 
using namespace std;
FILE *fpRead1,*fpRead2,*fp;	
template<class T>
class AdvancedOperation
{
//	int a[],int b[],int c[],int n,int m
	public:
		AdvancedOperation();
		void add();
		void sub();
		void multi();
		int SubStract(int a[], int b[], int n, int m );
		int divide();
		void translate();
	private:
	    int i;
		int c[3000];
		char numberN[1500], numberM[1500];	
		int* a;
		int* b;
		int* r;
		int n, m;
};
template<class T>
AdvancedOperation<T>::AdvancedOperation()
{
    
}
template<class T>
void AdvancedOperation<T>::translate()
{
	//文件读取,存入数组中 
	fpRead1 = fopen("E:\\C++\\高精度算法\\data1.txt", "r");
	if (fpRead1 == NULL){  
        cout<<"Open File Failed."<<endl;  
        exit(0);  
    } 
    //得到文件内数据的长度 
    fseek(fpRead1,0,SEEK_SET);
    fseek(fpRead1,0,SEEK_END);
    long length1=ftell(fpRead1);// length1就是文件的长度
    
    fseek(fpRead1,0,SEEK_SET);     //文件流位置放到开头
	for(i=0;i<length1;i++){   
	    fscanf(fpRead1, "%s", &numberN[i]); //读取单个数据 
    }  	
    
    fpRead2 = fopen("E:\\C++\\高精度算法\\data2.txt", "r");
    if (fpRead2 == NULL){  
        cout<<"Open File failed.\n";  
        exit(0);  
    } 
    //得到文件内数据的长度 
    fseek(fpRead2,0,SEEK_SET);
    fseek(fpRead2,0,SEEK_END);
    long length2=ftell(fpRead2);// length2就是文件的长度
    
    fseek(fpRead2,0,SEEK_SET);  //文件流位置放到开头 
	for(i=0;i<length2;i++){  
    fscanf(fpRead2, "%s", &numberM[i]);  //读取单个数据 
    }  	
	    n = strlen(numberN);m = strlen(numberM);
		a=new int[n];b=new int[m];              //两个和数据等长的中间数组 
		int i, j;
		for (i = 0, j = n - 1; i < n; i++, j--) {//将字符数组转换为整型数数组 
			a[i] = numberN[j] - '0';
		}
		for (i = 0, j = m - 1; i < m; i++, j--) {//将字符数组转换为整型数数组 
			b[i] = numberM[j] - '0';
		}
		cout<<"a为:";
		for(i=n-1;i>=0;i--)cout<<a[i];
		cout<<endl;
		cout<<"b为:";
		for(i=m-1;i>=0;i--)cout<<b[i];
		cout<<endl<<endl;
	
}
template<class T>
void AdvancedOperation<T>::add()
{
		//cout<<"m="<<m<<" n="<<n<<endl;
		r=new int[3000];
		for(i=0;i<3000;i++)r[i]=0;
		
		    if(n>=m)
		{
			for(i=0;i<m;i++)
		    r[i]=a[i]+b[i];
		    
		    for(i=m;i<n;i++){
		    	r[i]+=a[i];
			}
	    }
	        else
	    {
	    	for(i=0;i<n;i++)
		    r[i]=a[i]+b[i];
		    for(i=n;i<m;i++)
			r[i]+=b[i]; 
		}
	
		for (i = 0; i < n + m; i++) {  //进位的处理 
		    if (r[i] >= 10) {
				r[i + 1] += r[i] / 10;
				r[i] %= 10;         
			}
		}
		int j;
		for ( j = 2999; j > 0; j--) {  //从后向前遍历,当最后一位不为零时,即为到达运算结果的最后一位(原数组全为0) 
			if (r[j] != 0)
				break;
		}
		fp=fopen("E:\\C++\\高精度算法\\add.txt","w");
		for (i = j; i >= 0; i--) {
			printf("%d", r[i]);
			fprintf(fp,"%d",r[i]);
		}
		printf("\n");
}
template<class T>
void AdvancedOperation<T>::sub()
{
	    int j;
		for(i=0;i<3000;i++)r[i]=0;
		if(n>=m)                          //做比较,分被减数数组大于减数数组和被减数数组小于减数数组两种情况 
		{                                 //被减数数组大于减数数组 
			for(i=0;i<m;i++)
		    r[i]=a[i]-b[i];
		    for(i=m;i<n;i++)r[i]+=a[i];
	    }
	    else if(n<m)                     //被减数数组小于减数数组 
	    {
	    	for(i=0;i<n;i++)
		    r[i]=a[i]-b[i];
		    for(i=n;i<m;i++)r[i]-=b[i]; 
		}
		//for (j = 2999; j > 0; j--) {  //从后向前遍历,当最后一位不为零时,即为到达运算结果的最后一位(原数组全为0) 
		//	if (r[j] != 0)
		//		break;
		//}  
		for (i = 0; i < j; i++) {  //进位的处理 
		    if (r[i] < 0) {
				r[i + 1] - 1;
				r[i] += 10;         
			}
		}
		for (j = 2999; j > 0; j--) {  //从后向前遍历,当最后一位不为零时, 
			if (r[j] != 0)            //即为到达运算结果的最后一位(原数组全为0)
				break;
		}
		fp=fopen("E:\\C++\\高精度算法\\sub.txt","w");
        for (i = j; i >= 0; i--) {
			printf("%d", r[i]);
			fprintf(fp,"%d",r[i]);
		}
		printf("\n");
}
template<class T>
void AdvancedOperation<T>::multi()
{
		int j;
	    for (i = 0; i < 3000; i++) r[i] = 0; 
		for (i = 0; i < n; i++) {
			for (j = 0; j < m; j++) {
				r[i + j] += a[i] * b[j];    //根据乘法性质来进行运算 
			}
		}
		for (i = 0; i < n + m; i++) {  //进位的处理 
			if (r[i] >= 10) {
				r[i + 1] += r[i] / 10;
				r[i] %= 10;         
			}
		}
		for (j = 2999; j > 0; j--) {  //从后向前遍历,当最后一位不为零时,即为到达运算结果的最后一位(原数组全为0) 
			if (r[j] != 0)
				break;
		}
		fp=fopen("E:\\C++\\高精度算法\\multi.txt","w");
		for (i = j; i >= 0; i--) {
			printf("%d", r[i]);
			fprintf(fp,"%d",r[i]);
		}
		printf("\n");
}
template<class T>
int AdvancedOperation<T>::SubStract(int a[], int b[], int n, int m )
{
	int i;  
    if( n < m )  
        return -1;  
    if( n == m )  
    {                        //判断a > b  
        for( i=n-1; i>=0; i-- )  
        {  
            if( a[i] > b[i] )   //若大,则满足条件,可做减法  
                break;  
            else if( a[i] < b[i] ) //否则返回-1  
                return -1;  
        }  
    }  
    for( i=0; i<=n-1; i++ )  //从低位开始做减法  
    {  
        a[i] -= b[i];  
        if( a[i] < 0 )          //若a<0,则需要借位  
        {  
            a[i] += 10;         //借1当10  
            a[i+1]--;           //高位减1  
        }  
    }
    
    for( i=n-1; i>=0; i-- )       //查找结果的最高位  
        if( a[i] )               //最高位第一个不为0  
            return (i+1);       //得到位数并返回  
    
    return 0;                  //两数相等的时候返回0  
}
template<class T>
int AdvancedOperation<T>::divide()
{
	int j; 
    int nTimes;                 //两大数相差位数  
    int nTemp;                  //Subtract函数返回值  
    for (i = 0; i < 3000; i++) r[i] = 0;   //结果数组全部置零
	//int temp[n];                //temp数组暂存a数组内容,用于求余数
	//for(j=0;j<n;j++)temp[j]=a[j]; 
 
        if( n < m )   //如果被除数小于除数,结果为0  
        {  
            cout<<"0"<<endl;    
        }  
        nTimes = n - m;    //相差位数  
        for ( i=n-1; i>=0; i-- )    //将除数扩大,使得除数和被除数位数相等  
        {  
            if ( i>=nTimes )  
                b[i] = b[i-nTimes];  
            else                     //低位置0  
                b[i] = 0;  
        }  
        m = n;  
        for(j=0; j<=nTimes; j++ )      //重复调用,同时记录减成功的次数,即为商  
        {  
            while((nTemp = SubStract(a,b + j,n,m - j)) >= 0)  
            {  
                n = nTemp;      //结果长度  
                r[nTimes-j]++; //每成功减一次,将商的相应位加1  
            }  
        }  
  
        //输出结果  
        for( i=2999; r[i]==0 && i>=0; i-- );//跳过高位0  
        if( i>=0 ) 
		      {
                for( ; i>=0; i-- )  
                cout<<r[i];
			  }
        else  
            cout<<"0";  
        cout<<endl; 
        cout<<"余数为:";
		for( i=n-1; i>=0; i-- )cout<<a[i];
        cout<<endl; 
        
        fp=fopen("E:\\C++\\高精度算法\\divide.txt","w");
		for (i = j-1; i >= 0; i--) {
			fprintf(fp,"%d",r[i]);
		}
		printf("\n");
		fprintf(fp,"\n%s","余数为:");
		for( i=n-1; i>=0; i-- )fprintf(fp,"%d",a[i]);
		printf("\n");
		
        return 0; 
}
#endif

.

##另一种思路##
结构设计:

采用双向循环列表,将长达一百多位的长整数分成很多部分存在链表结点中,并且采用万进制,在每个结点内存储最多四位数字,通过两个链表不同结点之间的运算来实现长整数的运算。

算法设计及分析:

整个程序包含共四个算法,完成加减乘除四种运算。

加运算:对于循环链表中的不同结点,从尾结点开始,依次向前遍历,对两个链表的相同位置结点进行加运算,得到新的结果存入第一个链表的结点,并考虑进位情况。如果两个链表长度不一样,从尾结点向前,直至某一链表结束,另一链表前面的结店直接连接到相加后的结点前面,作为新的结点。

减运算:和加运算类似,不过符号取反,以加运算来计算减运算。

乘运算:乘法运算相比较于加法运算,其复杂度更高。在运算过程中,对于每一条链表的每一个结点,都要和另一个链表的所有结点相乘并求和,作为新的链表结点,并且在相乘过程中,还要考虑进制与结点位置因素,例如尾结点与尾结点的前一结点相乘,就会有乘以结点内进制(结点内数据以万进制存储就乘以10^4),当所有结点均与另一链表的所有结点相乘并累加,即可得出乘法运算结果。

除运算:除运算和乘运算的算法思想差异很大,除法运算是减法的另一种使用 。在除法运算中,被除数与除数是倍数加余数的关系,一个被除数除以一个除数,相当于用被除数不断减去这个除数,当倍数变为小于或等于除数时,运算结束,减运算运行的次数就是商,剩下的被除数就是除运算的余数。

分析与探讨:

在这个程序中,首次使用了双向循环链表,并以结点为单位存储数据。

用两个链表,每个结点保存一位数,在链表头保存数的正负,正数保存1,负数保存-1,如果是正数,后面每一位都储存正数,负数每一位都储存负数,数字按照链表头到链表尾从高位到低位的顺序。

从两个链表的尾部开始同步向前相加,加完存到第一个链表,第二个加完的结点销毁,会存在两个链表不一样长的情况,没进行加运算的直接连到链表的前面,最高位的符号存到链表的头。

根据表头的符号,调整后面的数字的值,中间会产生进位或者退位的问题,各个结点的符号不一定相同,但对于正负数都可以建立同样的调整模式,将正负到tmp中(1或-1)加或者减(tmp10),然后对前一位加或者减tmp1即可。第一位保留符号不变,这样不用处理多余的进位,也就不用再产生新的结点,也不用保存符号。

从前到后遍历已经处理好的表,将每一位进行输出就可以了。

虽然算法思路很清晰,实现却很困难,在乘法运算中,近百行代码用于结点之间的循环相乘,并且每次乘法之后还要考虑进位和进制问题,使得算法十分复杂。


大二小白的课设题目,程序中还有很多可以改进的地方,大家看看做下参考就好~

  • 8
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南木Sir

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值