巨大数的输入输出及其简单计算

余生长醉 提交于 2020-02-06 12:33:32

一、巨大数的基本概念

巨大数的基本概念:位数趋近于无限的数字。

二、巨大数的由来在这里插入代码片

当前计算机对于数据的处理在一般情况下,只能处理一些固定类型并且固定长度的数据。例如,在对于c语言提供了许多整数类型,int类型(范围:-2147483648~2147483647),float类型(可表示的数据范围是 -3.4E+38 和 3.4E+38)和double类型(可表示的数据范围-1.7E-308~1.7E+308),但是float的有效位数只有7位,double类型的有效位数也只有15位,处理数据不够精确。要处理的数据的位数越多,丢失的信息量也越大。因此我们提供一种对位数超出表示范围的数据的处理工具——也就是这个巨大数的处理工具。  

三、万进制基本概念

万进制的原理同二进制十进制是一样的,二进制是逢二进一,同理万进制就是逢万进一。
1,首先用字符串(char hugeNum[ ])存储巨大数。
2,用atoi函数将字符串转化为整形(int类型)的数组,并且按照四个一组的方式存储到整形数组中。这就需要记录数字的长度,而且还需要考虑符号位的问题。
由此我们可以的出计算巨大数所需要的基本数据 :
1. int sign		巨大数的符号
2. int *huge	巨大数的数据
3. int count	巨大数的数据位数

即:

typedef struct HUGE_NUMBER {
	int sign; /* 取值为 0 或 1 ,其中0 代表正号, 1代表负号(符号域)*/
	int *huge; /* 巨大数的整数部分(数据域)*/
	int count;/* 数据的位数(十进制情况下)*/
}

四、为什么是万进制而不是十万进制

因为我们决定用整形数组的方式存储巨大数,为了方便而使用int类型。之所以使用万进制,而不用更高的进制位,是因为在涉及乘法的计算中依然要是数据保持在int类型可以表示的范围之内(例:若使用十万进制,当出现99999×99999 = 9999800001,但是很明显9999800001已经超出了int类型的表示范围,因此一万进制是最合适的进位)。

五、微易码补码的概念

获得微易码补码的具体操作与求补码相似,除符号位外,对于整数,其微易码补码与原码相同,对于负数,其微易码补码为9999-源码(huge[ i ])。
例如:
1.正数
源码:12 3456				微易码补码:12 3456
2.负数
源码:- 12 3456				微易码补码:- 9987 6543
代码如下:
int mecCode(int  huge, int sign) {
	return ((sign == 1) ? (9999 - huge) : huge); /*对于负数,其微易码补码为9999-源码(huge[ i ])。*/
} /*返回值为微易码编码*/

六、引入《微易码补码》的原因

参考计算机内部的减法运算是通过加法运算来实现的,并且在计算减去一个负数时,实际时加上一个整数。采用补码的概念可以有效的合并加减法为加法,从而简化对负数操作的复杂运算。

七,用实例局部证实,并推导微易码补码进行加、减法所需执行要点,及其正确性

1.加法的实现

在进行加法前,先将巨大数的***数据域逆置,***便于计算进位问题。
void addHugeNum(HUGE_NUMBER *one, HUGE_NUMBER *two, HUGE_NUMBER* sum) {
	int tempNum = 0;
	int i = 0;
	int carry = 0;
	***/*注意在结果动态申请空间的过程中申请int类型空间的数量为 (count+ 3)/4  +  1,用于有进位的情况*/***
	sum->count = ((((one->count > two->count ? one->count : two->count) + 3) / 4) + 1) * 4;
	sum->huge = (int*) calloc(sizeof(int), (sum->count + 3) /4);

	for (i = 0; i < ((sum->count + 3) / 4); i++) {
		if (i >= (one->count + 3) / 4) {
			one->huge[i] = 0;
		}
		if (i >= (two->count)) {
			two->huge[i] = 0;
		}
		tempNum = getMecCode(one->huge[i], one->sign) + getMecCode(two->huge[i], two->sign) + carry;
		sum->huge[i] = tempNum % 10000;
		carry = tempNum / 10000;
	}
	***/*符号为由第一个数的符号位,第二个数的符号位以及加法过程最后的进位进行异或(^)运算的到*/***
	sum->sign = one->sign ^ two->sign ^ carry;
	for (i = 0; i < ((sum->count + 3) / 4); i++) {
		tempNum = sum->huge[i] + carry;
		sum->huge[i] = getMecCode(tempNum % 10000, sum->sign);
		carry = tempNum / 10000;
	}
}
加法的验证:
1. 正加正
 	0(+)1200 3456										 	      0(+)3456 		1200		0000
 +	0(+) 9900 0001				    ————> +  				  0(+)0001 		9900		0000
 —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
 														       0(0^0^0)3457		1100 		0001(进位为0)
 2.正加负
 	 0(+)1200 3456								          	  0(+)3456 		1200
 +	 1(-) 9900 0001				 	————> +  	  			  1(-) 9998		0099
 ——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
 												 			    1(0^1^0)3454(1)    1300(进位为0)
 														结果为负数,得到的是微易码补码,需要转化为源码。即按万进制用9999-源码。
 	 3.负加负
 	 0(-)1200 3456								          	  1(-)6543 		8799		9999
 +	 1(-)1000 0001					————> +  				  1(-) 9998		8999		9999
 —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
 																1(0^1^0)6541(1)    7799(1)		10000(进位为1)				
 														结果为负数,得到的是微易码补码,需要转化为源码。即按万进制用9999-源码。故,结果为:0000		2200		3458 比正确结果多1。
经过反复多次实验可以得出一个结论在取得微易码补码的结果时,需要给补码加上最后的进位。
因此 - 1200 3456 -(-)1000 0001 的微易码补码为 :10000 7798 6542
计算原码的结果为:0000 2200 3457

2.减法的实现

减法运算是在加法的基础上进行的,在进行减法时,把其中一个数变为它的相反数,再调用加法函数即可。
void subtraction(HUGE_NUMBER one, HUGE_NUMBER two, HUGE_NUMBER* sum) {
	if (two.sign == 0) {
		two.sign = 1;
	} 
	else if(two.sign == 1){
		two.sign = 0;
	}
	addHugeNum(&one, &two, sum);
}

3.乘法的实现

							2222				2222
		*      				2222				2222
		 ——————————————————————————————————————————————————————————
		 			493		7777				7284(493)
	  		 493   7777		7284
	  ______________________________________________________________		
	  		 493	8271 	5061				7284		
乘法不需要用到微易码补码,只需要将一般的十进制乘法转变成万进制乘法就可以了。
代码实现如下:
void multiplication(HUGE_NUMBER* num1, HUGE_NUMBER* num2, HUGE_NUMBER* result) {
	int i = 0;
	int j = 0;
	int carry = 0;
	int resIndex = 0;
	int eachResult = 0;

	result->count = ((((num1->count > num2->count ? num1->count : num2->count) + 3) / 4) * 2) * 4;
	result->huge = (int*)calloc(sizeof(int), (result->count + 3) / 4);
	result->sign = (num1->sign == num2->sign ? 0 : 1);

	for (i = 0; i < (num2->count + 3) / 4; i++) {
		carry = 0;
		resIndex = i;
		for (j = 0; j < (num1->count + 3) / 4; j++) {
			eachResult = num1->huge[j] * num2->huge[i] + carry;
			carry = (eachResult + result->huge[resIndex]) / 10000;
			result->huge[resIndex] = (eachResult + result->huge[resIndex]) % 10000;
			resIndex++;
		}
		result->huge[resIndex] = carry;
	}
}

代码实现分析:
1.result->huge[0] = 7284, result->huge[1] = 7777, result->huge[2] = 493,
2.result->huge[1] = 7284 + 7777 = 5061 (1),result->huge[2] = 7777 + 493 + (1) = 8271, result->huge[3] = 493,
故,结果为:
result->huge[3] = 493,
result->huge[2] =8271
result->huge[1] = 5061
result->huge[0] = 7284
即:493 8271 5061 7284

八、诚恳承认,由于数学能力不足,无法给出微易码补码正确性的数学证明

九、总结

首先,感谢铁血教主的指导。通过巨大数这个项目的练习,加深了我对C语言函数调用,指针方面的理解。老师说C语言是考验程序员基本功语言,也是考验程序员素质的语言。如果大量的malloc,calloc申请空间,却不进行free释放,会产生严重的内存泄漏。 所以,在C编程中,一定一定记得申请空间,完了就要释放空间。通过让我想到了计算机中补码存在的意义,我想,计算机中补码存在的原因,可能其中之一就是为了利用加法实现减法吧。用微易码补码的方式来实现加法,确实是一种非常巧妙的处理方法。
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!