巨大数实现

巨大数(整数)

前言

在计算机中,任何一个数据类型的大小都是有限制的。

数据类型 长度(字节) 识别方式 范围
char 1 ASCII码 -2^7(-128) ~ 2^7-1(127)
short 2 补码 -2^15(-32 768) ~ 2^15-1(32 767)
int 4 补码 -2^31(-2 147 483 648) ~ 2^31-1(2 147 483 647)
long 4 补码 -2^31(-2 147 483 648) ~ 2^31-1(2 147 483 647)
float 4 浮点 -/+3.4e38(精确到6位小数)
double 8 浮点 -/+1.7e308(精确到15位小数)

那么,在日常生活中,数的大小是不确定的。 当我们需要表示一个很大的数时,如何使用有限的空间存储无限的数呢?

一、万进制

这里提出一种方法:万进制

其实他和十进制、二进制、八进制、十六进制原理是一样的。只是他是逢万进一。


为什么采用万进制?

——》 起初,我首先想到使用字符串数组进行存储数据,这样确实比较好存储,也比较容易输出。但是进行相加时就会出现一系列问题,我们需要将每个字符先转换为数字,更麻烦的就是进位的问题。加减法是还比较好控制,到了乘法,就非常难以控制了。并且,完成之后,又得不字符转换为字符储存起来,这又是一次赋值操作。

——》然后,我想的是用一个char数组分别存储每一个字符转化的数,但是这样乘法所涉及的进位也是一个麻烦的事。

——》最后,我想到了采用万进制来进行数的存储。

在这里插入图片描述

扫描二维码关注公众号,回复: 11586452 查看本文章

只需要多申请一个长度去处理进位即可。

二、微易码反码

微易码反码是我们老师(教主)多年来编程经验所总结出来的。在进行相关运算时,是真的香。

这是关于原码补码反码的简单介绍:原码、补码、反码介绍

在万进制下:正数的反码就是本身,而负数的反码是9999 - 这个负数的绝对值。下面用图来介绍微易码反码!

其中:0表示正数,1表示负数
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

总结:两数相加时,申请万进制下的n(n为最大数的值)+1个空间。正数不变,负数用9999 - 绝对值;

结果的符号为:第一个数的符号位 ^ 第二个数的符号位 ^ 进位的数值(0或1)

结果为:最后一位加进位的数值,再根据符号位进行转换。

为什么要再多申请一位空间呢?忽略数值内的进位问题,将进位拿出来单独处理。否则就像上面的例子一样,结果出现偏差。

三、解决巨大数实现前期问题

1、分组

在不确定数值的长度下,我们该如何将它切分为四位一组呢?

n / 4 ?这明显存在一个漏洞。n不是四的整数倍时,n /4 的余数是无法计算到的。那我们可以使(n + 3) / 4 。这样不会使得超出一个长度,也会将 n / 4 的余数也一起照顾到。

2、存储顺序

万进制应该使用哪个数据结构来储存呢? char? short? int ! 前面两个的数值表示大小不足以暂时存储后面乘法的进位问题。

还有一个问题,我们是应该从头开始存储还是从尾开始存储。有些人就开始问了,我们一般表示数不都是从头部开始的吗?

这里注意手工过程!若从头开始存储,我们相加的时候怎么办呢?我们怎么进行从最后一位开始相加呢(这里指的是万进制下的个位)。

若我们从个位依次向前存储,那么就很轻松的解决了一个问题,位数对齐。这就使得后面的运算简单了起来。

3、存储结构

根据前面的说法,我们需要这个数的符号,存储值的数组,还需要数组的个数。

现给出巨大数的存储结构

typedef struct HUGE {
	boolean sign; //符号
	int *huge; //存储数据
	int count; //数组大小
}HUGE;

4、读取方式

由于我们采用从文件里面读取数据,我们先确定数据的长度length,根据length去申请空间。同时我们也不确定这个数据是否存在符号位。所以,在读取数据之前我们先判断符号位,如果存在的话length–,并设置符号位;然后将文件读写指针移至文件末尾,使文件指针指向最后一位数据,使用fgetc()h获得这个字符,之后四位一组读取,依次存入数组中。共读取length个长度。

5、读取具体实现

boolean initHuge(HUGE **hugeer, FILE *fp) {
	int i;
	int j;
	int index;
	int count;
	int ch;
	int scale = 1;//因为四位一组的数据中需要逐次*10
	int tmp = 0;
	int hugeindex = 0;//巨大数数组下标
	
	//判断传入的数据是否符合规定
	if(NULL == hugeer || NULL != *hugeer || NULL == fp) {
		return FALSE;
	}

	*hugeer = (HUGE *)calloc(sizeof(HUGE), 1);
	
	fseek(fp, 0, SEEK_END);
	count = ftell(fp);//数据长度
	fseek(fp, 0, SEEK_SET);
	ch = fgetc(fp);//获取第一个字符,并且判断是否是'+'、'-'
	if(ch == '-') {
		(*hugeer)->sign = 1;
		i = count;
		--count;
	} else if(ch == '+') {
		(*hugeer)->sign = 0;
		i = count;
		--count;
	} else {
		(*hugeer)->sign = 0;
		i = count;
	}
	
    //申请数组空间,申请失败,释放申请的hugeer空间,并返回错误
	(*hugeer)->huge = (int *)calloc(sizeof(int), (count + 3) / 4);
	if(NULL == (*hugeer)->huge) {
		free(*hugeer);
		return FALSE;
	}
	
    //获取4的整数倍的数据
	for(index = 0; index < (count / 4); index++) {
		for(j = 0; j < 4; j++){
			fseek(fp, (--i) * sizeof(char), SEEK_SET);
			ch = getc(fp) -'0';
			ch *= scale;
			scale *= 10;
			tmp += ch;
		}
		(*hugeer)->huge[hugeindex++] = tmp;
		scale = 1;
		tmp = 0;
	}
	
    //若长度不是4的整数倍,执行下面代码。
	index = count % 4;
	if(index > 0) {
		while(index){
			fseek(fp, (--i) * sizeof(char), SEEK_SET);
				ch = getc(fp) -'0';
				ch *= scale;
				scale *= 10;
				tmp += ch;
				index--;
		}
		(*hugeer)->huge[hugeindex++] = tmp;
	}

	(*hugeer)->count = (count + 3) / 4;
	return TRUE;
}

四、巨大数的显示

因为我们是从尾开始存储的,那么,我们只需要反着输出即可,在输入的过程中,若某个位是1,会直接输出1。但是在数字中间,应该是0001,那么只需要%40d输出即可。

void ShowHuge(HUGE *hugeer) {
	int i = hugeer->count - 1;

	for(i; i >= 0; i--) {
		if(i == hugeer->count -1) {  //从数组最后一位开始输出,并且他是几就输出几。
			printf("%d",hugeer->huge[i] * (hugeer->sign == 0 ? 1 : -1));
		} else {
			printf("%04d",hugeer->huge[i]);
		}
	}
	printf("\n");
}

五、加减法的实现

根据前面微易码反码的铺垫,我们已经得知了具体的实现过程。先直接给出加法的代码。

//获得微易码反码
int getMECcode(int data, boolean sign) {
	return (sign == 0 ? data : (9999 - data));
}

boolean add(HUGE *hugeerone, HUGE *hugeerother, HUGE **hugeresult) {
	int i;
	int rescount;
	int result;
	int tmp = 0;
	int realresult;

	if(NULL == hugeresult || NULL != *hugeresult) {
		return FALSE;
	}
	*hugeresult = (HUGE *)calloc(sizeof(HUGE), 1);

	//申请两个巨大数中最大count + 1的数组空间。
	rescount = ((hugeerone->count) > (hugeerother->count) ? hugeerone->count : hugeerother->count) + 1;

	(*hugeresult)->huge = (int *)calloc(sizeof(int), rescount);

    //如果i大于一个巨大数的conut,那么,将这个巨大数i后面的数组值赋值为0
	for(i = 0; i < rescount; i++) {
		if(i >= hugeerone->count) {
			hugeerone->huge[i] = 0;
		}
		if(i >= hugeerother->count) {
			hugeerother->huge[i] = 0;
		}
		result = getMECcode(hugeerone->huge[i], hugeerone->sign) 
				 + getMECcode(hugeerother->huge[i], hugeerother->sign) 
				 + tmp;//+tmp表示需要加上次相加后的进位。
		(*hugeresult)->huge[i] = result % 10000;//只将10000内的值赋值给数组
		tmp = result / 10000;//获取进位。
	}
    
    //得到符号位 
	(*hugeresult)->sign = (hugeerone->sign) ^ (hugeerother->sign) ^ tmp;

	for(i = 0; i < rescount; i++) {
		realresult = (*hugeresult)->huge[i] + tmp;//加进位的数,完成最后的部分
		(*hugeresult)->huge[i] = getMECcode(realresult % 10000, (*hugeresult)->sign);//最后根据结果的符号将结果再通过微易码反码变回来。
		tmp = realresult / 10000;
	}
    //这里表示,若数组的最后一个数组值为0,说明没有存储有效数据,那么有效数据rescount--即可;
	if(0 == ((*hugeresult)->huge[rescount - 1])) {
		rescount--;
	}
	(*hugeresult)->count = rescount;

}

减法建立在加法的基础上,只需要将被减数符号变换即可。

boolean sub(HUGE *hugeerone, HUGE *hugeerother, HUGE **hugeresult) {
	hugeerother->sign = hugeerother->sign == 0 ? 1 : 0;
	add(hugeerone, hugeerother, hugeresult);
	hugeerother->sign = hugeerother->sign == 0 ? 1 : 0;
}

六、乘法的实现

乘法的基础是加法,是第二个数最后一位和第一个数全部进行相乘。然后第二个数倒数第二位和第一个数全部进行相乘。依次类推,结果相加。这时不需要再管符号位了。只是在最后把符号的符号赋值即可。

这里需要申请的数组大小是一个比较困难的问题。(我只能得出大概得出数字,但不能完全申请好,如果你有更好方案,欢迎可以和我交流交流)。

boolean mul(HUGE *hugeerone, HUGE *hugeerother, HUGE **hugeresult) {
	int i;
	int j;
	int rescount;
	int tmp = 0;
	int result;

	if(NULL == hugeresult || NULL != *hugeresult) {
		return FALSE;
	}
	
	rescount = (hugeerone->count) * (hugeerother->count);//不成熟的申请方式
	*hugeresult = (HUGE *)calloc(sizeof(HUGE), 1);
	(*hugeresult)->huge = (int *)calloc(sizeof(int), rescount);
	(*hugeresult)->sign = hugeerone->sign ^ hugeerother->sign;

    //从第二个的最后一位开始
	for(i = 0; i < hugeerother->count; i++) {
		tmp = 0;
		for(j = 0; j < hugeerone->count; j++){
			result = hugeerone->huge[j] * hugeerother->huge[i] + tmp;
			tmp = (result + (*hugeresult)->huge[i + j]) / 10000;
			(*hugeresult)->huge[i + j] = (result + (*hugeresult)->huge[i + j]) % 10000;//给i+j位赋值,原因是i不变,j持续加一,当i变化时,对应的数组下标也开始变化,且每次j循环结束后,刚好数组向后移动一位,即第二次赋值从倒数第二位开始,符合乘法的手工过程。
		}
		(*hugeresult)->huge[i + j] = tmp;
	}
    
    //解决多申请的空间。并不是释放,而是在显示时不显示。
	while((*hugeresult)->huge[rescount - 1] == 0) {
		rescount--;
	}

	(*hugeresult)->count = rescount;
}

七、总结

完成巨大数本身并不算太难(当然是因为有教主的微易码反码支持),在思考时,一切从手工过程出发,将手工过程变为代码,这是非常重要的一步。如果手工过程没有好好过一遍,编写代码真的是一件非常痛苦且困难的事。

因为时间的限制,没有完成巨大数的除法。这也是个小小的遗憾吧。

最后说一句,编代码之前必须先思考,想清楚了,再进行编程。

猜你喜欢

转载自blog.csdn.net/weixin_45483328/article/details/107979843