C语言实现超级大数的加法、乘法、阶乘


说明

超级大数的范围在 0~1e9223372036854775807,也就是最大是 99999........99999(中间9223372036854775807个9)

实现了:

  • 大数 + 大数 => 大数
  • 大数 x 个位数 => 大数
  • 大数 x 大数 => 大数
  • int阶乘 =>大数

所有参与运算的大数都要大于0,还没有做小于0的处理。

主要实现原理和算法思路

用数组表示大数,每一位表示一个数字。

加法就是将两个大数按位累加,处理进位就用一个额外的变量保存。

乘法 a x b 就是将b的每一位数字和a相乘(每一位都处理和判断进位问题),对应还要将它扩大10的n次方倍,再将所有的相乘的结果相加构成一个新的大数。就是结果。(和小学手算高位数乘法,列竖式,原理一样)

阶乘:不停的调用大数乘法。

缺点

用数组实现的大数,但是数组里面的每一位都用的一个int来表示一个个位数字,空间浪费严重。

时间复杂度可能依然待优化,计算6000的阶乘需要6秒左右。

使用了数组动态扩容的方法来存储大数,没有考虑到链表这种节点式存储结构

还没有实现负数,减法操作,因此只能表示正数

潜在的bug和漏洞

将字符串转成大数的过程中没有处理非数字字符,如果将含有非数字字符的字符串转化成大数,会出现错误。

大数销毁方法的好像还不干净彻底,经过测试发现循环打印1~1000的阶乘,内存占用会持续增长,累计占用达到了8G左右。

代码使用方法

把下面的代码全部复制到你的项目中去,具体的使用方法可以看代码中最下面的main函数里面的用法。以及每个函数的用法可以看具体的注释

源代码

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/**
* 大数类型
* 最大值理论上可以是
* 1e9223372036854775807
* 99999........99999(中间9223372036854775807个9)
*/
struct bigNum {
int *arr; // 位数数组
long long _arrLen; // 数组位数的当前最大长度
long long wei; // 有多少位 初始是1 表示现在这个数是个位数
} typedef BigNum;

/**
* 获取一个初始化的大数字对象,值为0
* @return 大数对象,值为零
*/
BigNum getZero() {
BigNum res;
res._arrLen = 2; // 设定一开始的位数
res.arr = (int *) malloc(sizeof(int) * res._arrLen);
res.wei = 1;
res.arr[0] = 0;
return res;
}

/**
* 销毁一个大数对象
* @param bigNum 大数对象地址
*/
void destroyBigNum(BigNum *bigNum) {
free(bigNum->arr);
}

/**
* 给大数对象中的数组扩容,扩容策略是
* 2, 4, 8, 16 每次空间不够时,都扩充成原来的两倍
* @param bigNum 需要扩容的大数对象地址
*/
void extendBigNum(BigNum *bigNum) {
bigNum->_arrLen *= 2;
bigNum->arr = (int *) realloc(bigNum->arr, sizeof(int) * bigNum->_arrLen);
}

// 整形乘方计算
int myPow(int n, int m) {
if (m == 0) {
return 1;
}
int res = n;
for (int i = 1; i < m; i++) {
res *= n;
}
return res;
}

/**
* 判断m是多少位数
* @param m
* @return 如果m是 169 则返回 3
*/
int getIntWei(int m) {
int n = 0;
do {
n++;
m /= 10;
} while (m > 0);
return n;
}

/**
* 将一个 int 数字转化成 BigNum类型
* @param int 输入的int类型数
* @return bigNum 大数对象
*/
BigNum intToBigNum(int integer) {
int n = getIntWei(integer);
BigNum res = getZero();
while (res._arrLen < n) {
extendBigNum(&res);
}
for (int i = 1; i <= n; i++) {
int dig = (integer % (myPow(10, i))) / myPow(10, (i - 1));
res.arr[i - 1] = dig;
}
res.wei = n;
return res;
}

/**
* 将数字字符串转化为大数对象
* @param string 字符串,如 "1234535543"
* 字符串只能是纯数字,如果出现其他字符会导致程序bug
* @return 大数对象
*/
BigNum stringToBigNum(char *string) {
BigNum res = getZero();
while (res._arrLen < strlen(string)) {
extendBigNum(&res);
}
for (long long i = 0; i < strlen(string); i++) {
res.arr[strlen(string) - i - 1] = (int) (string[i]) - 48;
}
res.wei = (long long) strlen(string);
return res;
}

/**
* 将大数打印成一行打印出来
* @param bigNum 大数对象地址
*/
void printBigNumLn(BigNum *bigNum) {
for (long long i = bigNum->wei - 1; i >= 0; i--) {
printf("%d", bigNum->arr[i]);
}
printf("/n");
}

/**
* 打印这个大数具体的信息
* 包含了直观表示,内部数组存储结构,位数和当前数组长度。
* @param bigNum 大数对象地址
*/
void printBigNumObj(BigNum *bigNum) {
for (long long i = bigNum->wei - 1; i >= 0; i--) {
printf("%d", bigNum->arr[i]);
}
printf(" [");
for (int i = 0; i < bigNum->wei; i++) {
printf("%d ", bigNum->arr[i]);
}
printf("]");
printf("arrLen:%lld;wei:%lld/n", bigNum->_arrLen, bigNum->wei);
}

/**
* 将这个大数对象增加它自己,也就是乘以2倍
* 输入 231 => 462
* @param bigN 大数对象地址
* 无返回值,函数调用直接修改大数对象自身的值
*/
void bigNumAddSelf(BigNum *bigN) {
// 扩容检测
if (bigN->arr[bigN->wei - 1] >= 5 && bigN->wei == bigN->_arrLen) {
extendBigNum(bigN);
}
int next = 0; // 下一位进位现象 0 无进位,1有进位
for (long long i = 0; i < bigN->wei; i++) {
int res = bigN->arr[i] * 2 + next;
if (res < 10) {
bigN->arr[i] = res;
next = 0;
} else {
bigN->arr[i] = res - 10;
next = 1;
}
// 判断最后是不是多出来一位
}
if (next == 1) {
// printf("up wei/n");
bigN->arr[bigN->wei] = 1;
bigN->wei++;
}
}

/**
* 把b2里面的内容拷贝到b1里面去
* 深拷贝
* copy: b1 <=== b2
* @param b1 大数对象地址 接收
* @param b2 大数对象地址 提供复制
* 无返回值,函数调用直接修改大数对象 b1 自身的值
*/
void bigNumCopy(BigNum *b1, BigNum *b2) {
if (b1->_arrLen < b2->_arrLen) {
// 扩容1
b1->_arrLen = b2->_arrLen;
b1->arr = (int *) realloc(b1->arr, sizeof(int) * b1->_arrLen);
}
// 先将b1内的数据清零
for (long long i = 0; i < b1->_arrLen; i++) {
b1->arr[i] = 0;
}
// 一位一位复制
for (long long i = 0; i < b2->wei; i++) {
b1->arr[i] = b2->arr[i];
}
b1->wei = b2->wei;
}

/**
* 大数比较大小,
* @param b1 大数对象地址
* @param b2 大数对象地址
* @return 如果 b1 > b2 返回 1 ; 如果 b1 < b2 返回 -1;相等返回 0
*/
int bigNumCmp(BigNum *b1, BigNum *b2) {
if (b1->wei > b2->wei) {
return 1;
} else if (b1->wei < b2->wei) {
return -1;
} else {
// 从高位向低位比较
for (long long i = b1->wei - 1; i >= 0; i--) {
if (b1->arr[i] > b2->arr[i]) {
return 1;
} else if (b1->arr[i] < b2->arr[i]) {
return -1;
}
}
return 0;
}
}

/**
* 大数相加 有返回值版
* @param b1 大数对象地址
* @param b2 大数对象地址
* @return 返回一个新的大数对象
*/
BigNum bigNumPlus(BigNum *b1, BigNum *b2) {
// 注意左边的参数的值比右边的要大
if (bigNumCmp(b1, b2) == -1) {
BigNum *temp = b1;
b1 = b2;
b2 = temp;
}
BigNum res = getZero();
if (b1->wei == b1->_arrLen) {
extendBigNum(b1); // 最高位刚好达到数组了就直接扩容,不判断了
}
bigNumCopy(&res, b1);
// b2 要扩容到和res一样的水平
if (b2->_arrLen < res._arrLen) {
b2->_arrLen = res._arrLen;
b2->arr = (int *) realloc(b2->arr, sizeof(int) * b2->_arrLen);
// 多扩容出来的数据要清零
for (long long i = b2->wei; i < b2->_arrLen; i++) {
b2->arr[i] = 0;
}
}
while (b2->_arrLen <= res._arrLen) {
extendBigNum(b2);
}
int next = 0; // 下一位进位现象 0 无进位,1有进位
for (long long i = 0; i < res.wei; i++) {
int resNum = b2->arr[i] + res.arr[i] + next;
if (resNum < 10) {
res.arr[i] = resNum;
next = 0;
} else {
res.arr[i] = resNum - 10;
next = 1;
}
}
// 判断最后是不是多出来一位
if (next == 1) {
res.arr[res.wei] = 1;
res.wei++;
}
return res;
}

/**
* 大数相加 无返回值版
* 把b2加到b1上
* @param b1 大数对象地址
* @param b2 大数对象地址
* 无返回值,直接通过地址修改了b1的值。
*/
void bigNumAdd(BigNum *b1, BigNum *b2) {
if (b1->wei == b1->_arrLen) {
extendBigNum(b1); // 最高位刚好达到数组了就直接扩容,不判断了
}
int flag = bigNumCmp(b1, b2);
if (flag == -1) {
// 后边大,前面小
while (b1->_arrLen < b2->_arrLen) {
extendBigNum(b1);
}
// 前面0化
for (long long i = b1->wei; i < b1->_arrLen; i++) {
b1->arr[i] = 0;
}
}
int next = 0; // 下一位进位现象 0 无进位,1有进位
for (long long i = 0; i < b2->wei; i++) {
int resNum = b1->arr[i] + b2->arr[i] + next;
if (resNum < 10) {
b1->arr[i] = resNum;
next = 0;
} else {
b1->arr[i] = resNum - 10;
next = 1;
}
}
if (b2->wei > b1->wei) {
b1->wei = b2->wei;
}
// 判断最后是不是多出来一位
if (next == 1) {
b1->arr[b1->wei]++;
b1->wei++;
}
}

/**
* 将一个大数乘以多少倍数,倍数是个位数(<10)
* @param bgn 大数对象地址
* @param a 自觉保证是个位数
* 无返回值,函数调用直接修改 bgn 的值
*/
void bigNumMulDig(BigNum *bgn, int a) {
// 乘以0要特殊处理
if (a == 0) {
bgn->wei = 1;
bgn->arr[0] = 0;
return;
}
// 如果这个数最高位刚好顶满了数组,那么可能就会有数组溢出的风险,直接扩容
if (bgn->wei == bgn->_arrLen) {
extendBigNum(bgn);
}
int next = 0;
for (long long i = 0; i < bgn->wei; i++) {
int resNum = bgn->arr[i] * a + next;
if (resNum < 10) {
bgn->arr[i] = resNum;
next = 0;
} else {
next = resNum / 10;
bgn->arr[i] = resNum % 10;
}
}
if (next) {
bgn->arr[bgn->wei] = next;
bgn->wei++;
}
}

/**
* 将一个大数对象的结尾增加n个零
* @param bgn 大数对象
* @param a 增加几个0,如果是1就是扩大十倍,如果是2就是扩大100倍,如果是0就是不扩大
* 例如:
* (大数=1124, a=3) => 大数变成:1124000
* 无返回值,函数调用直接修改 bgn 的值
*/
void bigNumAppend0(BigNum *bgn, long long a) {
if (a <= 0) {
return;
}
while (bgn->wei + a > bgn->_arrLen) {
extendBigNum(bgn);
}
for (long long i = bgn->wei - 1; i >= 0; i--) {
bgn->arr[i + a] = bgn->arr[i];
}
for (int i = 0; i < a; i++) {
bgn->arr[i] = 0;
}
bgn->wei += a;
}

/**
* 大数乘法 b1 * b2
* @param b1
* @param b2
* @return 返回大数乘法后的结果
*/
BigNum bigNumMul(BigNum *b1, BigNum *b2) {
BigNum res = getZero();
// 注意左边的参数的值比右边的要大
if (bigNumCmp(b1, b2) == -1) {
BigNum *temp = b1;
b1 = b2;
b2 = temp;
}
for (long long i = 0; i < b2->wei; i++) {
// 遍历b2的每一位
int w = b2->arr[i];
BigNum newBg = getZero();
bigNumCopy(&newBg, b1); // 被乘数b1复制一份
bigNumMulDig(&newBg, w);
bigNumAppend0(&newBg, i);
bigNumAdd(&res, &newBg);
}
return res;
}

/**
* 阶乘
* @param n 数字
* @return 阶乘的结果
*/
BigNum jie(int n) {
BigNum res = getZero();
res.arr[0] = 1;
for (int i = 1; i <= n; i++) {
BigNum bigI = intToBigNum(i);
res = bigNumMul(&res, &bigI);
destroyBigNum(&bigI);
}
return res;
}

int main() {
// 大数相加
BigNum b1 = intToBigNum(1);
printBigNumObj(&b1);
BigNum b2 = intToBigNum(95599);
printBigNumObj(&b2);
BigNum b3 = bigNumPlus(&b1, &b2);
printBigNumObj(&b3);

// 大数乘以个位数
BigNum bg1 = intToBigNum(11665544);
bigNumMulDig(&bg1, 6);
printBigNumObj(&bg1);

// 大数乘以大数
BigNum bg3 = intToBigNum(166655466);
BigNum bg4 = intToBigNum(443335544);
BigNum bg5 = bigNumMul(&bg3, &bg4);
printBigNumObj(&bg5);

// 字符串版 大数乘以大数
BigNum bgn1 = stringToBigNum("8717829120915684686898899879870");
BigNum bgn2 = stringToBigNum("1555888888564684684684684888888");
BigNum bgn3 = bigNumMul(&bgn1, &bgn2);
printBigNumObj(&bgn3);

// 遍历输出阶乘结果
// for (int i = 1; i < 1000; i++) {
// BigNum result = jie(i);
// printf("!%d/n", i);
// // printBigNumObj(&result);
// printBigNumLn(&result);
// destroyBigNum(&result);
// }

// 计算超大阶乘
BigNum result = jie(6000);
printBigNumLn(&result);
}