大数四则运算

大数的四则运算

    历经10天时间完成了整数巨大数的四则运算,一方面由于太懒没有同时完成小数巨大数部分,另一方面小数巨大书的完成也是建立在整数的基础之上,这也是没有同时完成小数部分的一个主要原因。完成整数是一个摸索的过程,完成之后在技术上有一定的成熟,再转向小数效率会更加的高,整体大框架也会更好,不必大作改动。

    实现大数的目的在于,解决超出计算机可表示范围数据的存储以及运算,计算机中各类型数据可表示的范围见头文件limist.h,这里摘取部分代码,以int为例当超过-217483648~2147483647范围数字计算机就不能正常表示,需要设计一种数据结构来存储大数,大数在密码学中广泛运用,大数的运算往往涉及上百千位的运算。本篇文章将重点讲述大数的存储以及大数的四则运算。

#define MB_LEN_MAX    2             /* max. # bytes in multibyte char */
#define SHRT_MIN    (-32768)        /* minimum (signed) short value */
#define SHRT_MAX      32767         /* maximum (signed) short value */
#define USHRT_MAX     0xffff        /* maximum unsigned short value */
#define INT_MIN     (-2147483647 - 1) /* minimum (signed) int value */
#define INT_MAX       2147483647    /* maximum (signed) int value */
#define UINT_MAX      0xffffffff    /* maximum unsigned int value */
#define LONG_MIN    (-2147483647L - 1) /* minimum (signed) long value */
#define LONG_MAX      2147483647L   /* maximum (signed) long value */
#define ULONG_MAX     0xffffffffUL  /* maximum unsigned long value */

#if     _INTEGRAL_MAX_BITS >= 8
#define _I8_MIN     (-127i8 - 1)    /* minimum signed 8 bit value */
#define _I8_MAX       127i8         /* maximum signed 8 bit value */
#define _UI8_MAX      0xffui8       /* maximum unsigned 8 bit value */
#endif

#if     _INTEGRAL_MAX_BITS >= 16
#define _I16_MIN    (-32767i16 - 1) /* minimum signed 16 bit value */
#define _I16_MAX      32767i16      /* maximum signed 16 bit value */
#define _UI16_MAX     0xffffui16    /* maximum unsigned 16 bit value */
#endif

#if     _INTEGRAL_MAX_BITS >= 32
#define _I32_MIN    (-2147483647i32 - 1) /* minimum signed 32 bit value */
#define _I32_MAX      2147483647i32 /* maximum signed 32 bit value */
#define _UI32_MAX     0xffffffffui32 /* maximum unsigned 32 bit value */
#endif

1、大数的存储

    大数获取采取从txt文件中获得,将得到的数据按照万进制存储到int的数据,这里解释一下我们采取的为什么是万进制而不是更大的进制,因为不仅要完成巨大数的存储还要完成四则运算,在涉及乘法的部分,10000*10000小于整型的上限2147483647,不会产生溢出的情况,而更大的进制10万相乘则会溢出,同时从文件中读取的数据为十进制数字,而10000是10的次方,在转换进制的方面万进制提供了很好的便利,只需要每4位取出一个十进制数字存放到数组中即可,所以说10000是最合适的进制。

    那么根据上述分析可以得到大数的结构体如下,number_sign来表示大数的正负,0正1负,number_array表示巨大数的数据部分,number_count记录数组的长度。

typedef struct HUGENUMBER {
	char number_sign;			//标记正负符号
	int *number_array;		//指向存储巨大数的数组
	int number_count;			//数组中元素个数
}HUGENUMBER;

读取文件的时候我们先得到文件中数据的长度file_length,假设文件提供的数据都是合法字符,不存在字母的情况,这里需要注意的是文件中可能存放数据第一个字符可能是’-’或者’+’,巨大数的实际长度就需要相应变化,如果读到的第一个字符是0-9之间说明没有正负号字符,反之则存在正负号字符需要对上面得到的file_length减一。根据得到的文件长度就可以去申请存放巨大数数组空间,数组的长度number_count = (file_length + 3) / 4,存放的时候先存放巨大数最高位的数据,因为最高位的数字不一定是4个,例如234 5268 5982,先存放高位的234,存放最高位数字技巧如下:

在把巨大数从文件中存放到结构体数组的函数里,给出了一个只读数组,作用在与存放最大数的最高位数字,通过file_length % 4,当做数组下标取出字符串,当做fscanf函数的格式符便可取出最高位的数字。

const char *format[4] = {"%4d", "%1d","%2d", "%3d"};  

fscanf(fp,format[file_length % 4], &myNumber->number_array[myNumber->number_count- 1]);

剩下数字都可以每4位取得。存放的方式遵循将巨大数高位存放在数组高位,低位存放在低位,为后续的加减提供便利,存储的部分就完成。如下图

存储数据部分代码如下

//The read file operation in this section is only for integer operations,
// not counting the decimal, and needs to be modified.
void initHugeNumber(HUGENUMBER *myNumber, FILE *fp) {
	int file_length;
	int beginIndex;
	int i;
	char firstSign;
	const char *format[4] = {"%4d", "%1d", "%2d", "%3d"};		

	fseek(fp, 0, SEEK_END);    
	file_length = ftell(fp);
	fseek(fp, 0, SEEK_SET);
	fread(&firstSign, sizeof(char), 1, fp);							
//先读一个字符判断是不是数字,决定巨大数实际起始位置
	beginIndex = ('0' <= firstSign && firstSign <= '9') ? 0 : 1;    
//文件中巨大数实际实际起始位置
	file_length += ('0' <= firstSign && firstSign <= '9') ? 0 : -1;
 //文件中巨大数实际长度 以此来计算申请数组长度
	fseek(fp, beginIndex, SEEK_SET);
	myNumber->number_sign = (firstSign == '-') ? NEGATIVE_SIGN : POSTIVE_SIGN;
	myNumber->number_count = (file_length + 3) / 4;
	myNumber->number_array = (int *)calloc(sizeof(int), (file_length + 3) / 4);
	
	fscanf(fp, format[file_length % 4], &myNumber->number_array[myNumber->number_count - 1]);
	for(i = myNumber->number_count - 2; i >= 0; i--) {
		fscanf(fp, "%4d", &myNumber->number_array[i]);
	}

	fclose(fp);
}

2、巨大数的加法

普通的巨大数加法实现:上述存储部分是按照万进制存储到数组中,加的时候只需要从低位开始向高位按位相加,注意进位的问题。但是此种做法如果涉及负数相加,也就是减法运算则需要借位,并且借位需要仔细考虑所有情况。为了避免借位这一问题。

这里引入一个新的东西,微易码补码(这个是教主提出,教给我们的)!不过是万进制的补码,正数补码则是正数本身,被减数-减数就等价于被减数+-减数),可以将加减运算均看成加法来考虑,这样子问题就转换为只考虑如何完成两个巨大数的相加。

微易码补码的引进在极大程度上解决了巨大数减法运算中需要借位这一大难题,同时将加减运算均合并成加法解决,这种通过补码解决巨大数减法相比其他解决巨大数相减的方法,无疑是秒杀等级的。

例如巨大数源码为-234 5695 9532,其对应的补码就是9765 4304 0467

进行运算的时候,将两个巨大数均转换为补码的形式去完成运算,正数补码就是本身,负数补码就是对应9999-本身,而长度不相等的两个巨大数,巨大数位数不够的时候补0,最终负数前面补的0相加的时候会转为对应的补码9999,通过加法得到的结果巨大数长度,结果一定为两个加数最长的那个巨大数长度还多一个空间,那么在申请数组空间的时候就申请位数最大的巨大数number_count + 1,也决定了相加的循环次数。

结果的符号位通过三个值按位异或,carry_data^number1_sign^number^sign,进位数字和两个加数的符号三个数字进行异或运算得到结果的正负。

补码的使用只在加减运算的时候转换,在读文件存储数据的时候存进去都是原码,最后计算得到的结果同样是补码的形式,需要将其转换为源码形式返回,也避免了输出的时候源码补码的转换问题。



加法主要代码如下,其中在加法函数中调用了两个关键函数indexOf()和getMecCode()函数。

int indexOf(HUGENUMBER myNumber, int current_Index) {
	return (current_Index >= myNumber.number_count) ? MIN_BIT : myNumber.number_array[current_Index];
}

int getMecCode(int value, char number_sign) {
	return (number_sign == NEGATIVE_SIGN) ? (MAX_BIT - value) : value;
}
HUGENUMBER addHugeNumber(HUGENUMBER myNumber1, HUGENUMBER myNumber2) {
	int i;
	int carry_data;
	HUGENUMBER addResult = {0};
	
	addResult.number_count = ((myNumber1.number_count > myNumber2.number_count)? myNumber1.number_count : myNumber2.number_count) + 1; 					//以最长的巨大数数组长度+1作为总循环次数	
	addResult.number_array = (int *)calloc(sizeof(int), addResult.number_count); 	//永远多申请一块数组空间,也有可能这块空间会被浪费
	
	for(i = 0, carry_data = 0; i < addResult.number_count; i++) {
		int eachResult = getMecCode(indexOf(myNumber1, i), myNumber1.number_sign) + getMecCode(indexOf(myNumber2, i), myNumber2.number_sign);
		addResult.number_array[i] = (eachResult + carry_data) % CURRENT_SYSTEM;
		carry_data = (eachResult + carry_data) / CURRENT_SYSTEM;					//考虑连续进位的情况存在..			
	}
	addResult.number_sign = myNumber1.number_sign ^ myNumber2.number_sign ^ carry_data;

	for(i = 0; i < addResult.number_count; i++) {
		int value = addResult.number_array[i] + carry_data;
		addResult.number_array[i] = getMecCode(value % CURRENT_SYSTEM, addResult.number_sign);
		carry_data = value / CURRENT_SYSTEM;
	}
						
	return eliminateZeros(&addResult);
} 

先调用indexOf函数是为了判断巨大数长度不相等的时候给前面补0,而getMecCode函数是将源码转为补码,逐个讲解函数此处作用。

int indexOf(HUGENUMBER myNumber, int current_Index)

current_index:进行当前加法运算循环的次数,

myNumber:进行加法运算的一个巨大数

如果当前的循环次数超过了巨大数的位数也就是number_count,返回0。

当前循环次数还在巨大数的位数之内,返回current_index下标处数组的值的巨大数值。

int getMecCode(int value, char number_sign)

此函数就是根据传递进来参数的正负得到该数字的补码,代码极为简单,如果是正数也就number_sign == 0,补码就是该数本身,返回传递进来的值。如果是负数number_sign== 1,负数补码就是9999减去本身,返回9999- value

3、巨大数的减法

减法在上述加法的基础之上,也是通过加法完成,被减数-减数 =被减数+(-减数)完成。只需要将减数的符号位变为相反,再使其进行加法即可完成减法。

HUGENUMBER subHugeNumber(HUGENUMBER myNumber1, HUGENUMBER myNumber2) {
	HUGENUMBER subResult = {0};

	myNumber2.number_sign = !myNumber2.number_sign;
	subResult = addHugeNumber(myNumber1, myNumber2);

	return eliminateZeros(&subResult);
}

4、巨大数的乘法

乘法的实现原理和十进制的实现相同,按位相乘即可。

两个乘数相乘的积的数组长度:最大应为两个乘数的长度之和。

mulResult.number_count=multiplicandNumber.number_count+ multiplierNumber.number_count;无论如何也不会超过这个长度比如99*99=9801

相乘得到的积符号位:两个乘数符号位按位异或,负负得正,正正得正,负正得负。

需要注意的地方是:相乘得到积是一个把每次相乘结果加起来的过程,就需要定义一个每次生成积的开始位置,作用域在循环体中。

int result_index = i;

HUGENUMBER mulHugeNumber(HUGENUMBER multiplicandNumber, HUGENUMBER multiplierNumber) {
	int i;
	int j;
	HUGENUMBER mulResult = {0};

	mulResult.number_count = multiplicandNumber.number_count + multiplierNumber.number_count;
	mulResult.number_array = (int *)calloc(sizeof(int), mulResult.number_count);
	mulResult.number_sign = multiplicandNumber.number_sign ^ multiplierNumber.number_sign;    //1^0=1,1^1=0,0^0=0

	for(i = 0; i < multiplierNumber.number_count; i++) {
		int carry_data = 0;
		int result_index = i;
		for(j = 0; j < multiplicandNumber.number_count; j++) {
			int eachResult = multiplierNumber.number_array[i] * multiplicandNumber.number_array[j] + carry_data;
			carry_data = (mulResult.number_array[result_index] + eachResult) / CURRENT_SYSTEM;
			mulResult.number_array[result_index] = (mulResult.number_array[result_index] + eachResult) % CURRENT_SYSTEM;
			result_index++;
		}
		mulResult.number_array[result_index] = carry_data;
	}

	return eliminateZeros(&mulResult);
}

4、巨大数的除法

巨大数的除法可以说是所有问题中最麻烦的部分,完成巨大数的项目如果没有完成除法无异于画龙无鳞,最初的思路是直接去除,巨大数的大小远远超过了上限直接去除不太可能,消耗了很长的时间并没有得出有效的方法。

思考计算机中除法运算机制,计算机cpu只会做加法和位移运算,平时使用计算机进行加减都可以通过加法和位移完成,二进制下的除法也是通过位移运算去达到除法的目的,位移运算是对一个数据放大缩小2的次方倍数,那么除法完全可以通过减法来实现,除法的本质就是减法,在上面巨大数的减法基础上,利用减法去完成除法,如果直接让被除数减去除数,如果被除数和除数相差很大,减法将被多次执行,大幅度提高了时间消耗,需要把除数放大使得放大后的除数去尽可能接近被除数,再利用减法去让被除数减除数,记录执行减法次数,以这样的大致思路来解决除法。此外,直接对一个数据进行位移运算,数字的大小是扩大缩小2的次方倍数,这里则需要扩大缩小10的次方倍数,就不能直接去位移放大数据,而需要通过乘法来放大数据。这一部分最难的地方就在每次如何确定除数放大的倍数,在保证放大后的除数不超过被除数的前提下寻找最大合理的倍数,这里需要仔细考虑,需要准备一些工具函数来确定这个放大倍数。

如何确定每次放大除数的倍数呢,例如:345678926432/1246578

2345678926432长度为13,246578长度是7,两个大数长度之差为6,那么就在除数后面补6个0,也就是放大1000000倍,再让放大后的除数和被除数比较,如果放大后的除数小于被除数,则该放大倍数可取,反之该放大倍数应缩小10倍,也就是放大10 0000,这种方法可以确定每次的倍数,但是需要调用巨大数乘法运算,会增大整体时间消耗,舍弃掉了这种确定倍数的方法。

最终的方法是:同时从高位遍历两个巨大数,1、 如果除数的第一个最高位数字大于被除数最高位第一个数字,返回FASLE。2、如果除数的第一个最高位数字小于被除数最高位第一个数字,返回TRUE。3、如果除数的第一个最高位数字等于被除数最高位第一个数字最高位数字继续重复1 2。虽然增加了代码量,但是避免掉了用乘法去检验倍数是否合理,减少了时间消耗。


工具函数int getHighestNum(int *array, inthugeNumber_length)是为了得到每次巨大数高位的一个数字,传递进去参数是巨大数数组和巨大数的长度,由于巨大数的最高位数字不一定存放的是4位数字,就需要通过长度来计算高位的数字个数。


确定倍数之后除法就轻而易举完成,不断的执行减法的过程,减法的次数乘以当前的倍数累加起来就是商,为了方便减法,在运行减法之前把被除数和除数都变为正数,具体减法过程可见下图。



这里是把除法运算的结果表示成一个商和余数的结果,如果需要表示成小数形式,那么就继续放大余数作为被除数,再让被除数重复上述减法,注意的是需要记录一个权重,最终结果表示的时候应是一个科学计数法,何时停止就给定一个保留小数点后几位,整数除法运算表示成商和余数也更合理。

//为getMinDivResult()函数服务,从巨大数高位开始逐个判断数字大小,
//1 如果除数的第一个最高位数字大于被除数最高位第一个数字,直接返回FASLE
//2 如果除数的第一个最高位数字小于被除数最高位第一个数字,直接返回TRUE
//如果除数的第一个最高位数字等于被除数最高位第一个数字最高位数字继续重复1 2
boolean compareHighestNum(HUGENUMBER dividendHug, HUGENUMBER divisorHug) {
	int num1_length;
	int num2_length;
	int judgeTimes;
	int divisor_num;
	int dividend_num;

	num1_length = getHugenumerLength(dividendHug);
	num2_length = getHugenumerLength(divisorHug);
	judgeTimes = num1_length > num2_length ? num2_length : num1_length;

	while(judgeTimes > 0) {
		dividend_num = getHighestNum(dividendHug.number_array, num1_length);
		divisor_num = getHighestNum(divisorHug.number_array, num2_length);
		if(divisor_num > dividend_num) {
//如果除数最高位数字大于被除数最高位数字 直接返回FASLE
			return FALSE;
		}else if(divisor_num < dividend_num) {
//除数的第一个最高位数字小于被除数最高位第一个数字,直接返回TRUE
			return TRUE;
		}else {
//除数的第一个最高位数字等于被除数最高位第一个数字最高位数字继续重复上述判断
			num1_length--;
			num2_length--;
		}
		judgeTimes--;
	}

	return TRUE;
}
//每次返回巨大数最高位的一个数字如21 1526 3545,第一次返回2第二次返回1
int getHighestNum(int *array, int hugeNumber_length) {

	return hugeNumber_length % 4 == 0 ? (array[((hugeNumber_length + 3) / 4) - 1] / THOUSAND) : (array[((hugeNumber_length + 3) / 4) - 1] / (int)pow(10.0, (double)((hugeNumber_length % 4) - 1))) % 10;
}
HUGENUMBER getMinDivResult(HUGENUMBER dividendHug, HUGENUMBER divisorHug) {
	HUGENUMBER Amplification = {0};
	int i;
	int Difference_length;

//通过调用compareHighestNum()函数来比较两个巨大数最高位的大小决定应该给除数补几个0
	Difference_length = compareHighestNum(dividendHug, divisorHug) ? (getHugenumerLength(dividendHug) - getHugenumerLength(divisorHug)) : (getHugenumerLength(dividendHug) - getHugenumerLength(divisorHug) - 1);
	Amplification.number_sign = POSTIVE_SIGN;
	Amplification.number_count = (Difference_length + 4) / 4;			
	Amplification.number_array = (int *)calloc(sizeof(int), Amplification.number_count);

	for(i = 0; i < Amplification.number_count - 1; i++) {
		Amplification.number_array[i] = 0;					//倍数的数据除去最高位都是0
	}

//还是需要通过两巨大数长度之差,例如之差为5时,目标倍数为0010 0000,则得到10的过程就是pow(10, 5 % 4) 注:在gcc下double转为int类型 会出现100变为99的情况
	Amplification.number_array[Amplification.number_count - 1] = (int)pow(10.0, (double)(Difference_length % 4));

	return Amplification;
}
//通过减法完成的除法
HUGENUMBER iterationSubtracting(HUGENUMBER dividendHug, HUGENUMBER divisorHug) {
	HUGENUMBER divResult = {0};													//除法运算的商
	HUGENUMBER biggerDivsorHug = {0};											//用来保存放大后的除数
	HUGENUMBER amplification = {0};		

	while(CompareHugeNumber(divisorHug, dividendHug) == LESS_THAN) {
		amplification = getMinDivResult(dividendHug, divisorHug);  				//得到倍数
		biggerDivsorHug = removeHugenumber(dividendHug, divisorHug);			//左移除数
		while(CompareHugeNumber(biggerDivsorHug, dividendHug) == LESS_THAN) {
			divResult = addHugeNumber(divResult, amplification);				//保存商
			dividendHug = subHugeNumber(dividendHug, biggerDivsorHug);			//开始被除数减去除数的减法运算
		}
	}

	destoryHugnumber(&biggerDivsorHug);
	destoryHugnumber(&lification);

	return eliminateZeros(&divResult);
}

    另外一个解决巨大数除法的思路是通过试商去找最终结果商,给定一个商的最小最大可能,在这个区间里面不断逼近商, 问题转换为一个一维搜索问题,这里搜索算法选的是二分查找法,把每次得到的区间中点和除数相乘,再和被除数做差值,直到差值绝对值小于被除数,说明此时区间中点就是最终结果,迭代停止。

这里我们把被除数用dividend表示,除数用divisor表示,每次的试商用tryResult表示,每次的试商可以表示为下列式子


用二分搜索法完成的除法是一个试商的过程,逐步缩小正确商所存在区间,去逼近商,是一个搜索目标值的过程,二分法在一维搜索里面收敛速度很快,每次迭代区间长度变化成半减少,最坏最好情况都是一样的,没有特殊性,但是采取试商的思路,通过搜索算法去解决巨大数除法无论如何都避免不了巨大数相乘。而通过减法来完成的除法是一个累加商的过程,不断的通过减法来累加商的值。

用二分法搜索商完成的除法和用减法实现的除数比较了一下,还是减法的时间要快一点,因为减法实现的除法不过多涉及巨大数的相乘,只在放大倍数地方进行一次相乘,而二分搜索算法,每次取得到区间中点都需要进行一次乘法再和被除数做差值,通过差值的正负和差值的绝对值大小来确定是否停止迭代,时间主要消耗在了试商的相乘部分。而减法中不涉及过多的乘法,只在放大除数处进行一次乘法,如果去掉这一次乘法,可以进一步提高性能。还考虑过用黄金分割搜索法来搜索商,黄金分割法在区间很大的时候开始缩小区间很快,但当区间缩小到一定范围时再去迭代很让人头疼,仔细想了想无论用什么搜索算法都没办法避免相乘,还是用二分法来逼近。

//二分查找得到区间的中点
HUGENUMBER getBinaryMid(HUGENUMBER lowerNumber, HUGENUMBER highNumber) {
	HUGENUMBER result = {0};
	int i;
	int carry_data;

	result = addHugeNumber(lowerNumber, highNumber);
	for(i = result.number_count - 1, carry_data = 0; i >= 0; i--) {
		int eachResult = result.number_array[i] + carry_data * 10000;
		result.number_array[i] = eachResult / 2;
		carry_data = eachResult % 2;
	}

	return result;
}

//二分搜索算法
//过在初始区间上进行二分搜索 根据每次区间中点更新搜索区间 逼近最终商
////每次取得的区间中点和除数相乘,再让被除数减去试商 * 除数得到差值
//搜索迭代条件为:差值小于0或者 差值绝对值大于除数绝对值
//直到差值绝对值小于除数并且差值大于0, 停止搜索
HUGENUMBER BinarySearchMethod(HUGENUMBER dividendHug, HUGENUMBER divisorHug) {
	HUGENUMBER divResult = {0};													//搜索最终结果
	HUGENUMBER low_range = {0};													//搜索区间的下限
	HUGENUMBER high_range = {0};												//搜索区间的上限
	HUGENUMBER differenceWithDivdend = {0};			

	low_range = getMinDivResult(dividendHug, divisorHug);   									//得到搜索商区间的初始下限
	high_range = getHighRange(low_range);														//得到搜索区间的上限
	divResult = getBinaryMid(high_range, low_range);
	differenceWithDivdend = subHugeNumber(dividendHug, mulHugeNumber(divisorHug, divResult));	//计算差值
	//开始二分搜索查找商
	while((CompareHugeNumber(divisorHug, differenceWithDivdend) == LESS_THAN) || (differenceWithDivdend.number_sign == NEGATIVE_SIGN)) {
		if(differenceWithDivdend.number_sign == NEGATIVE_SIGN) {
			high_range = divResult;													//此处表示当前商过于大了 应该更新区间上限为小的方向
		}else {
			if(CompareHugeNumber(differenceWithDivdend, divisorHug) == LESS_THAN) { //此处差值的值为正 判断值是否小于除数 
				break;																//如果差值小于除数,则搜索停止 已经找到商 余数就是此时的差值
			}else {
				low_range = divResult;												//如果差值不小于除数,说明当前试商过于小,更新区间下限为大的方向	
			}
		}
		divResult = getBinaryMid(high_range, low_range);
		differenceWithDivdend = subHugeNumber(dividendHug, mulHugeNumber(divisorHug, divResult));
	}

	destoryHugnumber(&low_range);
	destoryHugnumber(&high_range);
	destoryHugnumber(&differenceWithDivdend);

	return divResult;
}
HUGENUMBER getHighRange(HUGENUMBER low_range) {
	int i;
	HUGENUMBER high_range = {0};

	for(i = 0; i < 10; i++) {
		high_range = addHugeNumber(low_range, high_range);
	}
	
	return high_range;
}

除法

//巨大数除法 提供两个算法可完成除法   迭代相减divThroughSub   二分搜索BinarySearchMethod 均可完成除法
HUGENUMBER divHugeNumber(HUGENUMBER dividendNumber, HUGENUMBER divisorNumber) {
	char sign;
	HUGENUMBER divResult = {0};													//除法运算的商

	sign = divisorNumber.number_sign ^ divisorNumber.number_sign;
	divisorNumber.number_sign = dividendNumber.number_sign = POSTIVE_SIGN;															
//如果被除数小于除数 直接返回商为0 余数就是被除数
	if(CompareHugeNumber(dividendNumber, divisorNumber) == LESS_THAN) {
		divResult.number_count = 1;
		divResult.number_array = (int *)calloc(sizeof(int), 1);
		return divResult;
	}

	// divResult = BinarySearchMethod(dividendHug, divisorHug);
	divResult = iterationSubtracting(dividendNumber, divisorNumber);
	divResult.number_sign = sign;

	return eliminateZeros(&divResult);
}

  

        要提高除法性能应避免出现大数相乘,向巨大数相加、移动巨大数的方向靠拢,计算机中进行的运算都可以通过加法和位移运算来实现,减法性能高原因就是通过补码完成的,模仿计算机二进制补码相加实现减法,避免借位。

        所以完成巨大数除法的时候摒弃搜索、试商的思路,应该思考在被除数减除数的地方如何减少时间,减法完成的除法主要时间消耗在通过乘法放大除数,既然每次是放大除数的倍数是一个10的次方数,是否可以放弃用乘法放大除数,移动除数到更高位低位空出来的部分补0,由于是在万进制中,一定会涉及数组中值的变化,而不是单纯的移动数组元素,这是重点要考虑的地方

    对于考虑问题的大局观,走一步看十步的能力,这一方面还有很长的路要走,对于一个着手要解决的问题,应先去分析最大的难题在哪里,就这一难点再去攻克。

     这里很多的算法都和数学息息相关,尤其是在这里的二分查找,运筹学与最优化算法中的一维搜索,当时没有好好学,还有一些部分逻辑和离散数学有关系,经过这一番练习才发现数学对于算法的重要性啊。

附上完整代码

Hugenumber.h

#ifndef _HUGE_NUMBER_H_
#define _HUGE_NUMBER_H_

#define POSTIVE_SIGN		0
#define NEGATIVE_SIGN		1

#define LESS_THAN			1
#define NOT_LESS_THAN		0

#define TRUE				1
#define FALSE				0
#define ERROR_FLAG			-1

#define ONE					1
#define ZEROS 				0
#define THOUSAND			1000

#define MAX_BIT				9999
#define MIN_BIT				0
#define CURRENT_SYSTEM		10000

typedef unsigned char boolean;

typedef struct HUGENUMBER {
	char number_sign;			//标记正负符号
	int *number_array;			//指向存储巨大数的数组
	int number_count;			//数组中元素个数
	short number_power;
}HUGENUMBER;

//函数fscanf原型int fscanf(FILE*stream, const char *format, [argument...])
//判断巨大数实际长度和4取余结果,用取余结果当做下标在format数组里取得字符串做fscanf格式符
//取余结果 0 1 2 3
//读取长度 4 1 2 3
boolean readFileInfor(HUGENUMBER *myNumber, const char *fileName);

//根据传递进来的文件指针,将文件数据存储到结构体中
void initHugeNumber(HUGENUMBER *myNumber, FILE *fp);

//销毁巨大数
void destoryHugnumber(HUGENUMBER *myNumber);

//以a+b = c的形式将两个整数巨大数相加结果返回,需注意的是要释放结果中的数组空间
HUGENUMBER addHugeNumber(HUGENUMBER myNumber1, HUGENUMBER myNumber2);

//两个巨大数相减等于 第一个数加第二个数的相反数
HUGENUMBER subHugeNumber(HUGENUMBER myNumber1, HUGENUMBER myNumber2);

//乘法运算申请的结果数组长度为乘数与被乘数长度之和
HUGENUMBER mulHugeNumber(HUGENUMBER myNumber1, HUGENUMBER myNumber2);

//根据被除数去减除数的原理实现巨大数除法
HUGENUMBER divHugeNumber(HUGENUMBER dividendHug, HUGENUMBER divisorHug);

//微易码补码主要处理负数如何存储到万进制数组方便后续的减法运算,实际上是正数+负数
//负数的补码是9999-数值,正数补码就是正数本身。
//方便的是 调用一次 原码转为补码 调用第二次 补码转为原码
int getMecCode(int value, char number_sign);

//在加法运算中,以最长的巨大数当做循环标准
//判断当前循环次数是否超过数组长度,超过返回0,未超过则返回对应下标数组值
int indexOf(HUGENUMBER myNumber, int current_Index);

//判断两个巨大数绝对值的大小
//hugenumber1 < hugenumber2返回LESS_THAN,
//hugenumber1 >= hugenumber2 返回NOT_LESS_THAN
int CompareHugeNumber(HUGENUMBER hugenumber1, HUGENUMBER hugenumber2);

//得到巨大数的实际长度 不包含高位0数字长度
int getHugenumerLength(HUGENUMBER myNumber);

//通过计算得到除数乘的最大倍数 10的某次方
HUGENUMBER getMinDivResult(HUGENUMBER dividendHug, HUGENUMBER divisorHug);

//得到二分搜索区间中点
HUGENUMBER getBinaryMid(HUGENUMBER lowerNumber, HUGENUMBER highNumber);

//二分搜索
HUGENUMBER BinarySearchMethod(HUGENUMBER dividendHug, HUGENUMBER divisorHug);

//二分搜索得到搜索区间上限
HUGENUMBER getHighRange(HUGENUMBER low_range);

//去除巨大数高位无效数据0的方法
HUGENUMBER eliminateZeros(HUGENUMBER *myNumber);

//比较巨大数最高位的一个数字大小
boolean compareHighestNum(HUGENUMBER dividendHug, HUGENUMBER divisorHug);

//通过减法完成的除法
HUGENUMBER iterationSubtracting(HUGENUMBER dividendHug, HUGENUMBER divisorHug);

//得到巨大数高位上的一个数字
int getHighestNum(int *array, int hugeNumber_length);
#endif

Hugenumber.c

#include<stdio.h>
#include<malloc.h>
#include<math.h>

#include"HugeNumber.h"

int indexOf(HUGENUMBER myNumber, int current_Index) {
	return (current_Index >= myNumber.number_count) ? MIN_BIT : myNumber.number_array[current_Index];
}

int getMecCode(int value, char number_sign) {
	return (number_sign == NEGATIVE_SIGN) ? (MAX_BIT - value) : value;
}

HUGENUMBER addHugeNumber(HUGENUMBER augendNumber, HUGENUMBER addendNumber) {
	int i;
	int carry_data;
	HUGENUMBER addResult = {0};
	
	addResult.number_count = ((augendNumber.number_count > addendNumber.number_count)? augendNumber.number_count : addendNumber.number_count) + 1; 					//以最长的巨大数数组长度+1作为总循环次数	
	addResult.number_array = (int *)calloc(sizeof(int), addResult.number_count); 	//永远多申请一块数组空间,也有可能这块空间会被浪费
	
	for(i = 0, carry_data = 0; i < addResult.number_count; i++) {
		int eachResult = getMecCode(indexOf(augendNumber, i), augendNumber.number_sign) + getMecCode(indexOf(addendNumber, i), addendNumber.number_sign);
		addResult.number_array[i] = (eachResult + carry_data) % CURRENT_SYSTEM;
		carry_data = (eachResult + carry_data) / CURRENT_SYSTEM;					//考虑连续进位的情况存在..			
	}
	addResult.number_sign = augendNumber.number_sign ^ addendNumber.number_sign ^ carry_data;

	for(i = 0; i < addResult.number_count; i++) {
		int value = addResult.number_array[i] + carry_data;
		addResult.number_array[i] = getMecCode(value % CURRENT_SYSTEM, addResult.number_sign);
		carry_data = value / CURRENT_SYSTEM;
	}
						
	return eliminateZeros(&addResult);
} 

HUGENUMBER subHugeNumber(HUGENUMBER minuendNumber, HUGENUMBER reductionNumber) {
	HUGENUMBER subResult = {0};

	reductionNumber.number_sign = !reductionNumber.number_sign;
	subResult = addHugeNumber(minuendNumber, reductionNumber);

	return eliminateZeros(&subResult);
}

HUGENUMBER mulHugeNumber(HUGENUMBER multiplicandNumber, HUGENUMBER multiplierNumber) {
	int i;
	int j;
	HUGENUMBER mulResult = {0};

	mulResult.number_count = multiplicandNumber.number_count + multiplierNumber.number_count;
	mulResult.number_array = (int *)calloc(sizeof(int), mulResult.number_count);
	mulResult.number_sign = multiplicandNumber.number_sign ^ multiplierNumber.number_sign;    //1^0=1,1^1=0,0^0=0

	for(i = 0; i < multiplierNumber.number_count; i++) {
		int carry_data = 0;
		int result_index = i;
		for(j = 0; j < multiplicandNumber.number_count; j++) {
			int eachResult = multiplierNumber.number_array[i] * multiplicandNumber.number_array[j] + carry_data;
			carry_data = (mulResult.number_array[result_index] + eachResult) / CURRENT_SYSTEM;
			mulResult.number_array[result_index] = (mulResult.number_array[result_index] + eachResult) % CURRENT_SYSTEM;
			result_index++;
		}
		mulResult.number_array[result_index] = carry_data;
	}

	return eliminateZeros(&mulResult);
}


//巨大数除法 提供两个算法可完成除法   迭代相减divThroughSub   二分搜索BinarySearchMethod 均可完成除法
HUGENUMBER divHugeNumber(HUGENUMBER dividendNumber, HUGENUMBER divisorNumber) {
	char sign;
	HUGENUMBER divResult = {0};													//除法运算的商

	sign = divisorNumber.number_sign ^ divisorNumber.number_sign;
	divisorNumber.number_sign = dividendNumber.number_sign = POSTIVE_SIGN;															
//如果被除数小于除数 直接返回商为0 余数就是被除数
	if(CompareHugeNumber(dividendNumber, divisorNumber) == LESS_THAN) {
		divResult.number_count = 1;
		divResult.number_array = (int *)calloc(sizeof(int), 1);
		return divResult;
	}

	divResult = BinarySearchMethod(dividendNumber, divisorNumber);
	// divResult = iterationSubtracting(dividendNumber, divisorNumber);
	divResult.number_sign = sign;

	return eliminateZeros(&divResult);
}

boolean readFileInfor(HUGENUMBER *myNumber, const char *fileName) {
	FILE *fp;

	fp = fopen(fileName, "r");
	if(fp == NULL) {
		printf("File is not exist!");
		return FALSE;
	}

	initHugeNumber(myNumber, fp);
	return TRUE;
}

//The read file operation in this section is only for integer operations,
// not counting the decimal, and needs to be modified.
void initHugeNumber(HUGENUMBER *myNumber, FILE *fp) {
	int file_length;
	int beginIndex;
	int i;
	char firstSign;
	const char *format[4] = {"%4d", "%1d", "%2d", "%3d"};		

	fseek(fp, 0, SEEK_END);    
	file_length = ftell(fp);
	fseek(fp, 0, SEEK_SET);
	fread(&firstSign, sizeof(char), 1, fp);							//先读一个字符判断是不是数字,决定巨大数实际起始位置
	beginIndex = ('0' <= firstSign && firstSign <= '9') ? 0 : 1;    //文件中巨大数实际实际起始位置
	file_length += ('0' <= firstSign && firstSign <= '9') ? 0 : -1; //文件中巨大数实际长度 以此来计算申请数组长度
	fseek(fp, beginIndex, SEEK_SET);
	myNumber->number_sign = (firstSign == '-') ? NEGATIVE_SIGN : POSTIVE_SIGN;
	myNumber->number_count = (file_length + 3) / 4;
	myNumber->number_array = (int *)calloc(sizeof(int), (file_length + 3) / 4);
	
	fscanf(fp, format[file_length % 4], &myNumber->number_array[myNumber->number_count - 1]);
	for(i = myNumber->number_count - 2; i >= 0; i--) {
		fscanf(fp, "%4d", &myNumber->number_array[i]);
	}

	fclose(fp);
}

//判断两个巨大数绝大小 为除法运算中何时停止减法服务
//hugenumber1 < hugenumber2 返回LESS_THAN
//hugenumber1 >= hugenumber2,返回NOT_LESS_THAN
int CompareHugeNumber(HUGENUMBER hugenumber1, HUGENUMBER hugenumber2) {
//在进行除法的时候一律改变成正数相除 判断大小直接让两数相减 
	return subHugeNumber(hugenumber1, hugenumber2).number_sign == NEGATIVE_SIGN ? LESS_THAN : NOT_LESS_THAN;
}

//为getMinDivResult()函数服务,从巨大数高位开始逐个判断数字大小,
//1 如果除数的第一个最高位数字大于被除数最高位第一个数字,直接返回FASLE
//2 如果除数的第一个最高位数字小于被除数最高位第一个数字,直接返回TRUE
//如果除数的第一个最高位数字等于被除数最高位第一个数字最高位数字继续重复1 2
boolean compareHighestNum(HUGENUMBER dividendHug, HUGENUMBER divisorHug) {
	int num1_length;
	int num2_length;
	int judgeTimes;
	int divisor_num;
	int dividend_num;

	num1_length = getHugenumerLength(dividendHug);
	num2_length = getHugenumerLength(divisorHug);
	judgeTimes = num1_length > num2_length ? num2_length : num1_length;

	while(judgeTimes > 0) {
		dividend_num = getHighestNum(dividendHug.number_array, num1_length);
		divisor_num = getHighestNum(divisorHug.number_array, num2_length);
		if(divisor_num > dividend_num) {
//如果除数最高位数字大于被除数最高位数字 直接返回FASLE
			return FALSE;
		}else if(divisor_num < dividend_num) {
//除数的第一个最高位数字小于被除数最高位第一个数字,直接返回TRUE
			return TRUE;
		}else {
//除数的第一个最高位数字等于被除数最高位第一个数字最高位数字继续重复上述判断
			num1_length--;
			num2_length--;
		}
		judgeTimes--;
	}

	return TRUE;
}

//每次返回巨大数最高位的一个数字如21 1526 3545,第一次返回2第二次返回1
int getHighestNum(int *array, int hugeNumber_length) {

	return hugeNumber_length % 4 == 0 ? (array[((hugeNumber_length + 3) / 4) - 1] / THOUSAND) : (array[((hugeNumber_length + 3) / 4) - 1] / (int)pow(10.0, (double)((hugeNumber_length % 4) - 1))) % 10;
}
//此函数作用得到初始商最小 例如23 4585 4396 / 12 4562   初始最小商就是1 0000
//10000也是初始二分搜索区间的下限
HUGENUMBER getMinDivResult(HUGENUMBER dividendHug, HUGENUMBER divisorHug) {
	HUGENUMBER Amplification = {0};
	int i;
	int Difference_length;

//通过调用compareHighestNum()函数来比较两个巨大数最高位的大小决定应该给除数补几个0
	Difference_length = compareHighestNum(dividendHug, divisorHug) ? (getHugenumerLength(dividendHug) - getHugenumerLength(divisorHug)) : (getHugenumerLength(dividendHug) - getHugenumerLength(divisorHug) - 1);
	Amplification.number_sign = POSTIVE_SIGN;
	Amplification.number_count = (Difference_length + 4) / 4;			
	Amplification.number_array = (int *)calloc(sizeof(int), Amplification.number_count);

	for(i = 0; i < Amplification.number_count - 1; i++) {
		Amplification.number_array[i] = 0;					//倍数的数据除去最高位都是0
	}

//还是需要通过两巨大数长度之差,例如之差为5时,目标倍数为0010 0000,则得到10的过程就是pow(10, 5 % 4) 注:在gcc下double转为int类型 会出现100变为99的情况
	Amplification.number_array[Amplification.number_count - 1] = (int)pow(10.0, (double)(Difference_length % 4));

	return Amplification;
}

//二分查找得到区间的中点
HUGENUMBER getBinaryMid(HUGENUMBER lowerNumber, HUGENUMBER highNumber) {
	HUGENUMBER result = {0};
	int i;
	int carry_data;

	result = addHugeNumber(lowerNumber, highNumber);
	for(i = result.number_count - 1, carry_data = 0; i >= 0; i--) {
		int eachResult = result.number_array[i] + carry_data * 10000;
		result.number_array[i] = eachResult / 2;
		carry_data = eachResult % 2;
	}

	return result;
}

//二分搜索算法
//过在初始区间上进行二分搜索 根据每次区间中点更新搜索区间 逼近最终商
////每次取得的区间中点和除数相乘,再让被除数减去试商 * 除数得到差值
//搜索迭代条件为:差值小于0或者 差值绝对值大于除数绝对值
//直到差值绝对值小于除数并且差值大于0, 停止搜索
HUGENUMBER BinarySearchMethod(HUGENUMBER dividendHug, HUGENUMBER divisorHug) {
	HUGENUMBER divResult = {0};													//搜索最终结果
	HUGENUMBER low_range = {0};													//搜索区间的下限
	HUGENUMBER high_range = {0};												//搜索区间的上限
	HUGENUMBER differenceWithDivdend = {0};			

	low_range = getMinDivResult(dividendHug, divisorHug);   									//得到搜索商区间的初始下限
	high_range = getHighRange(low_range);														//得到搜索区间的上限
	divResult = getBinaryMid(high_range, low_range);
	differenceWithDivdend = subHugeNumber(dividendHug, mulHugeNumber(divisorHug, divResult));	//计算差值
	//开始二分搜索查找商
	while((CompareHugeNumber(divisorHug, differenceWithDivdend) == LESS_THAN) || (differenceWithDivdend.number_sign == NEGATIVE_SIGN)) {
		if(differenceWithDivdend.number_sign == NEGATIVE_SIGN) {
			high_range = divResult;													//此处表示当前商过于大了 应该更新区间上限为小的方向
		}else {
			if(CompareHugeNumber(differenceWithDivdend, divisorHug) == LESS_THAN) { //此处差值的值为正 判断值是否小于除数 
				break;																//如果差值小于除数,则搜索停止 已经找到商 余数就是此时的差值
			}else {
				low_range = divResult;												//如果差值不小于除数,说明当前试商过于小,更新区间下限为大的方向	
			}
		}
		divResult = getBinaryMid(high_range, low_range);
		differenceWithDivdend = subHugeNumber(dividendHug, mulHugeNumber(divisorHug, divResult));
	}

	destoryHugnumber(&low_range);
	destoryHugnumber(&high_range);
	destoryHugnumber(&differenceWithDivdend);

	return divResult;
}


//通过减法完成的除法
HUGENUMBER iterationSubtracting(HUGENUMBER dividendHug, HUGENUMBER divisorHug) {
	HUGENUMBER divResult = {0};													//除法运算的商
	HUGENUMBER biggerDivsorHug = {0};											//用来保存放大后的除数
	HUGENUMBER amplification = {0};		

	while(CompareHugeNumber(divisorHug, dividendHug) == LESS_THAN) {
		amplification = getMinDivResult(dividendHug, divisorHug);  				//得到倍数
		biggerDivsorHug = mulHugeNumber(amplification, divisorHug);			//左移除数
		while(CompareHugeNumber(biggerDivsorHug, dividendHug) == LESS_THAN) {
			divResult = addHugeNumber(divResult, amplification);				//保存商
			dividendHug = subHugeNumber(dividendHug, biggerDivsorHug);			//开始被除数减去除数的减法运算
		}
	}

	destoryHugnumber(&biggerDivsorHug);
	destoryHugnumber(&lification);

	return eliminateZeros(&divResult);
}


HUGENUMBER getHighRange(HUGENUMBER low_range) {
	int i;
	HUGENUMBER high_range = {0};

	for(i = 0; i < 10; i++) {
		high_range = addHugeNumber(low_range, high_range);
	}
	
	return high_range;
}

//去掉巨大数中高位的无效0数字,0000 0000 0023 5985 = 0023 5985
//思路:先调用getHugenumerLength(HUGENUMBER myNumber)方法得到巨大数有效数字长度
//根据长度申请新空间,赋值,销毁旧巨大数,返回newHugenumber。
HUGENUMBER eliminateZeros(HUGENUMBER *myNumber) {
	int actual_length;
	HUGENUMBER newHugenumber = {0};
	int i;

	actual_length = getHugenumerLength(*myNumber);
	newHugenumber.number_sign = myNumber->number_sign;
	newHugenumber.number_count = (actual_length + 3) / 4;
	newHugenumber.number_array = (int *)calloc(sizeof(int), newHugenumber.number_count);

	for(i = 0; i < newHugenumber.number_count; i++) {
		newHugenumber.number_array[i] = myNumber->number_array[i];
	}

	destoryHugnumber(myNumber);

	return newHugenumber;
}
//得到一个巨大数在十进制下的位数实际长度 不包含最高位为0的
//例如0000 02354 9563 4182实际长度为11
//为函数getAmplification(HUGENUMBER dividendHug, HUGENUMBER divisorHug)服务
//为函数eliminateZeros(HUGENUMBER *myNumber)服务
int getHugenumerLength(HUGENUMBER myNumber) {
	const int array[] = {0, 10, 100, 1000, 10000};
	int i;
	int highestNum_length;
//从巨大数高位开始 知道遇到有效不为0的数字停止
	for(myNumber.number_count = myNumber.number_count - 1; myNumber.number_array[myNumber.number_count] == 0; myNumber.number_count--);
	
	for(i = 0; i < 4; i++) {
		if(myNumber.number_array[myNumber.number_count] >= array[i] && myNumber.number_array[myNumber.number_count] < array[i+1]) {	
			highestNum_length = i+1;			//记录巨大数最高位数字的位数
		}
	}

	return highestNum_length + myNumber.number_count * 4;
}

void destoryHugnumber(HUGENUMBER *myNumber) {
	if(myNumber->number_array == NULL) {
		return;
	}

	free(myNumber->number_array);
	myNumber->number_array = NULL;
}

test.c

#include<stdio.h>
#include<malloc.h>
#include<math.h>

#include"HugeNumber.h"

//巨大数高位先打印
void showHugeInfor(HUGENUMBER myNumber);
void showHugeInfor(HUGENUMBER myNumber) {
	int i;

	printf("[%c]", (myNumber.number_sign == NEGATIVE_SIGN)? '-' :'+');
	for(i = myNumber.number_count - 1; i >= 0; i--) {
		printf("%00004d ", myNumber.number_array[i]);
	}
	printf("\n");
}

int main(void) {
	const char *addNumFileName = "../file\\Num1.txt";
	const char *augendNumFileName = "../file\\Num2.txt";
	long before_time;
	long after_time;
	long total_Time;

	HUGENUMBER addNumber = {0};
	HUGENUMBER augendNumber = {0};
	HUGENUMBER result = {0};

	readFileInfor(&addNumber, addNumFileName);
	readFileInfor(&augendNumber, augendNumFileName);

	showHugeInfor(addNumber);
	showHugeInfor(augendNumber);

before_time = clock();
	// result = removeHugenumber(addNumber, augendNumber);
	result = divHugeNumber(addNumber, augendNumber);
	// result = addHugeNumber(addNumber, augendNumber);
after_time = clock();
total_Time = after_time - before_time;
	showHugeInfor(result);

printf("\n%d digits dividend by %d digits took", getHugenumerLength(addNumber),getHugenumerLength(augendNumber));
printf(" %ld.%03ld seconds\n", total_Time / 1000, total_Time % 1000);

	destoryHugnumber(&addNumber);
	destoryHugnumber(&augendNumber);
	destoryHugnumber(&result);

	return 0;
}














猜你喜欢

转载自blog.csdn.net/yvken_zh/article/details/79288872