什么是巨大数
巨大数其实就是有效数字位很大,可表示数的大小超过了int 的表示范围:[-217483648,2147483647],虽然float、double类型可表示的数的范围很大,分别为:3.4E-38~3.4E+38、1.7E-308~1.7E+308。但是它们的有效数字位却不大,分别为:6 – 7位、5 – 16位。这时我们需要一个可以表示很大有效数字位的数—巨大数。
目的
为了解决超出计算机可表示范围数据的存储以及运算,如果按照平常的计算方法便会无能为力,这时,便需要一种可以解决更大位数计算的方法,也就是巨大数四则运算所存在的意义。而且我们的巨大数还可以为小数,因此起到增加计算精度的作用,本篇文章将重点讲述巨大数的存储以及四则运算。
巨大数的加法
巨大数的存储
上面说到,巨大数会超过int、float、double的表示范围,所以我们不能用它们来存储巨大数,这里我们用字符串,也就是定义一个char类型的数组,来存储巨大数,然后将它转化为整型量,存储到int类型的数组中(采用高高低低原则:数组的高位存储的是数据的高位,数组的低位存储的是数据的低位),即数据的个十百千位的四个数,存储到数组的第一个元素上,再参与运算。
巨大数结构体
typedef struct HUGE_NUMBER
{
boolean sign; //存储巨大数符号
int *data; //存储巨大数的数字
int count; //数组元素的个数
int length; //巨大数位数
int pow; //权重,多少位小数
}HUGE_NUMBER;
万进制
这里我们引入万进制,主要是为了效率问题,万进制,顾名思义,以万进1,其实万进制存储也就是用int类型的数组,四位一存,然后四位四位的进行运算,和我们所理解的十进制其实大同小异。如果,万进制运算效率高,那么我们为什仫不采用十万进制呢? 这样运算效率会更高,因为万进制一个位存储的最大9999(大于9999要进位),在做乘法计算时即使9999乘以9999,依然可以暂时放在这个位上,因为每一个位都是int类型的,整体是一个int类型的数组,最后再做统一处理。
微易码补码
这是巨大数的核心技术,相当厉害。
- 微易码补码的原理,类似计算机中原码和补码的转化:正数,原码 = 补码;负数,补码 = 原码按位取反,末位加1。微易码补码的原理:正数,等于其本身;负数,9999 - 这个数的绝对值。
- 为什么要引入《微易码补码》?在进行加减法运算时,减法可以转化为加法运算,那么减法其实就是加法,再进行加法过程中,运算数是带符号位的,有可能是负数。所以我们引入微易码补码就是为了减少因为负数而引起的复杂运算,这样简化运算。
- 上面简单的举了个例子,来说明微易码补码,下面将详细介绍微易码补码的神奇之处:
- 加法运算的代码:
void addHUGE(HUGE_NUMBER *hn1, HUGE_NUMBER *hn2, HUGE_NUMBER *result)
{
int carry = 0;
int sum;
int i;
result->count = hn1->count > hn2->count ? (hn1->count + 1): (hn2->count + 1); //运算结果存在着进位的可能,所以要多申请一个空间;
//若不进位,正数,这个空间将会补 0000 ;负数,这个空间将会补 9999。
//建议可以变量跟踪一下。
result->data = (int *)calloc(sizeof(int), result->count);
for (i = 0; i < result->count; i++) {
if (i >= hn1->count) {
hn1->data[i] = 0;
}
if (i >= hn2->count) {
hn2->data[i] = 0;
}
sum = getMecCode(hn1->data[i], hn1->sign) + getMecCode(hn2->data[i], hn2->sign) + carry;
carry = sum / 10000;
sum = sum % 10000;
result->data[i] = sum;
}
result->data[0] += carry; //有进位情况下,运算的结果和正确结果相差1, 这里加上进位。
if (hn1->sign ^ hn2->sign ^ carry) { //得到运算结果的符号,若为负号,要将补码转化。
result->sign = NEGATIVE;
for (i = 0; i < result->count; i++) {
result->data[i] = getMecCode(result->data[i], result->sign);
}
}
else {
result->sign = POSITIVE;
}
}
int getMecCode(int data, char sign)
{
return ((sign == 1) ? (9999 - data) : data);
}
巨大数的减法
巨大数的减法和加法一样。减法就是将一个运算数取相反数,再调用加法函数,就ok了。
void subHUGE(HUGE_NUMBER *hn1, HUGE_NUMBER *hn2, HUGE_NUMBER *result)
{
hn2->sign = (hn2->sign == 1) ? 0 : 1;
addHUGE(hn1, hn2, result);
hn2->sign = (hn2->sign == 1) ? 0 : 1;
}
巨大数的乘法
巨大数的乘法比较简单,它不需要用到微易码补码,只需要用万进制存储,就OK了。
四位一存,每次相错4位。
void mulHUGE(HUGE_NUMBER *hn1, HUGE_NUMBER *hn2, HUGE_NUMBER *result)
{
int carry = 0;
int sum;
int i;
int j;
int t = 0;
result->count = (hn1->count > hn2->count ? hn1->count : hn2->count) * 2;
result->data = (int *)calloc(sizeof(int), result->count);
result->sign = hn1->sign ^ hn2->sign;
for (i = 0; i < hn1->count; i++) {
t = i;
for (j = 0; j <= hn2->count; j++) {
if (j == hn2->count) {
hn2->data[j] = 0;
}
sum = hn1->data[i] * hn2->data[j] + carry;
carry = sum / 10000;
sum = sum % 10000;
result->data[t] += sum; //错位相加
carry += result->data[t] / 10000; //保证每个数组元素中只存储四位
result->data[t] = result->data[t] % 10000;
t++;
}
}
}
运行结果:
总结
首先,感谢铁血教主的指导,在这个巨大数项目中,对于万进制和微易码补码的运用,我感到很凶悍,这些方法确实让人感到新颖,教主nb~。同时,在我完成过程中,觉得一定要注意“手工过程”和“变量跟踪”,许多错误在编写时,是很难发现的。所以,在编写前,先进行“手工过程”,而且一定要充足,这样整理和开阔你的思路,有时候你敲几个小时的代码,都不如半个小时的手工过程。同时和同学一起进行讨论也是一种开阔思维的方法。