文章目录
一、巨大数的基本概念
巨大数的基本概念:位数趋近于无限的数字。
二、巨大数的由来在这里插入代码片
当前计算机对于数据的处理在一般情况下,只能处理一些固定类型并且固定长度的数据。例如,在对于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编程中,一定一定记得申请空间,完了就要释放空间。通过让我想到了计算机中补码存在的意义,我想,计算机中补码存在的原因,可能其中之一就是为了利用加法实现减法吧。用微易码补码的方式来实现加法,确实是一种非常巧妙的处理方法。
来源:CSDN
作者:weixin_44985777
链接:https://blog.csdn.net/weixin_44985777/article/details/104184095