引入巨大数的原因:
因为在C语言中,例如int类型,因为int型常量的本质是4字节(32位)补码,当数值最大为2^31 - 1,即只能表示-2147483648 ~ 2147483647,而对于更大的数例如几十位几百位的数,则无法用系统原有类型存储了,此时就需要引入巨大数概念。
巨大数的存储思考:
对于巨大数的存储,很容易能想到用字符串存储,将每个字符换成数字进行计算,但这种方法一位一位运算过于繁琐,效率太低。
这时可以引入万进制的概念。
万进制:
万进制即类似十进制,上限为9,万进制上限为9999,9999再加一则进位,理想在C语言中存储应该表示为1,0。
实际这种想法可以扩展到各种进制,但因为乘法进位位数特别多,当采用十万进制时,五位×五位数据可能超过21亿,超过了int能存储的最大数,所以用十万进制就不能进行计算了。
在保证计算便利和数据合理的前提下,万进制是最适合的。
巨大数存储:
通过思考后,因为巨大数是通过字符串取4位转成数字存储的,所以巨大数数据应该用一个int数组存放,额外还有符号,为了计算方便还需要记录有效位数。
typedef struct HUGE{
boolean sign;
int *data;
int size;
}HUGE;
分别存储正负,数据,数据有效位数
巨大数的初始化:
明确了用结构体存储后,就该进行巨大数的获取(巨大数初始化)的思考了。
这次巨大数的获取是从文件获取的,所以包含一系列文件操作。
首先因为数据用数组的形式存储,数组以动态申请,申请时的位数需要提前知道。
对于文件数据位数获取,在之前的文件操作中有写到,就一笔带过就行。
boolean getsize(FILE *fp, int *size) {
fseek(fp, 0, SEEK_END);
(*size) = ftell(fp);
fseek(fp, 0, SEEK_SET);
}
得到size后,因为可能有一位是符号,则需要对第一个字符判断,若为符号,size需减一并且给sign赋值
if ('-' == ch) {
sign = 1;
size--;
} else {
sign = 0;
}
在获取到size后,数组中一个元素用于存4位巨大数,那么数组总元素个数是否是size / 4呢?
若巨大数为4的整数倍位,直接除4即可,但当位数不足4(%4结果不足4),直接除4会造成数组存储空间不足。
获取元素总数计算方法:count = (size + 3) / 4
至此,巨大数结构体的初始化所需要的基础数据就获取完成了,接下来是巨大数数据的获取。
对于巨大数数据的获取,因为数据在文件中是从高位存到低位,数据位数不一定是4的整数倍,这会造成数据最高位可能不足4位,所以对于最初的一组元素需要特别获取。
head = size % 4;
for (i = 0; i < head; i++) {
if (1 == sign) {
tmp[i] = fgetc(fp);
} else {
tmp[i] = ch;
ch = fgetc(fp);
}
}
if (0 != head){
huge->data[figure++] = atoi(tmp);
}
首先得到最高位数据是否够4位,若够则不用特别获取。
因为我们是从文件中获取数据,在符号的判断时做过一次ch = fgetc()
所以要注意第一位不要漏掉,有可能在符号判断已经获取了。
对于字符串转换成int数都采用atoi()
,十分方便。
剩余数据获取:
if (1 == sign) {
i = head + 1;
} else {
i = head;
}
if (1 == sign) {
ch = fgetc(fp);
}
while (i < size) {
for (j = 0; j < 4; j++) {
tmp[j] = ch;
ch = fgetc(fp);
i++;
}
huge->data[figure++] = atoi(tmp);
}
注:若为负数,ch一直是负号没有变,所以对负数剩余数据的获取需要额外进行一个赋值操作。
至此巨大数初始化及其获取就完成了,完整函数如下:
boolean initHuge(HUGE *huge, int size, FILE *fp) {
int count;
int head;
char tmp[4] = {0};
int figure = 0;
int i;
int j;
char ch;
boolean sign;
ch = fgetc(fp);
if ('-' == ch) {
sign = 1;
size--;
} else {
sign = 0;
}
count = (size + 3) / 4;
head = size % 4;
huge->sign = sign;
huge->size = size;
huge->data = (int *) calloc(sizeof(int), count);
if (NULL == huge->data) {
return FALSE;
}
for (i = 0; i < head; i++) {
if (1 == sign) {
tmp[i] = fgetc(fp);
} else {
tmp[i] = ch;
ch = fgetc(fp);
}
}
if (0 != head){
huge->data[figure++] = atoi(tmp);
}
if (1 == sign) {
i = head + 1;
} else {
i = head;
}
if (1 == sign) {
ch = fgetc(fp);
}
while (i < size) {
for (j = 0; j < 4; j++) {
tmp[j] = ch;
ch = fgetc(fp);
i++;
}
huge->data[figure++] = atoi(tmp);
}
return TRUE;
}
有了获取就可以完成巨大数的显示和销毁函数了:
boolean destoryData(HUGE huge) {
free(huge.data);
}
void showHuge(HUGE huge) {
int count;
int i = 0;
count = (huge.size + 3) / 4;
if (1 == huge.sign && 0 != huge.data[0]) {
printf("-");
}
printf("%d", huge.data[0]);
for (i = 1; i < count; i++) {
printf("%04d", huge.data[i]);
}
printf("\n");
}
在巨大数输出函数中,除前四位外,其他数据空位由0填补,而前四位不需补零输出。