初识高精度计算

时间经过:2025.10.10-2025.10.15


那是平平无奇的一天,我打开了洛谷,看见了一道题,要求进行阶乘的累加。

题目上赫然写着“高精度”,但我并不知道这是什么意思。于是我忽略了这三个字。

我心想,这也太简单了啊,秒了。

一顿操作猛如虎,一半AC一半WA。

一顿操作猛如虎

上网搜索“高精度计算”,就好像打开了一道新的大门。
接下来的几天,我一有空就在研究这个,加法,乘法,洛谷OJ通过了之后又研究减法,除法。一直到放弃。
在这过程中,我基本上是一边查一边写,一边问AI一边写
确实又对函数和数组熟悉了不少

不多说了,看看代码(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
//高精度计算
//仅支持正整数

//存在的问题
//1.输入的数字还是int,不能支持很大的数字
//改进思路:或许可以直接用循环输入数组
//2.各个函数对于边界的处理,我并不确定是否正确,比如i<LEN还是i<LEN-1还是i<LEN-2
//改进思路:以后再看
//3.sub函数和div函数内部,有可能会互换num1和num2,这就破坏了输入的数据
//改进思路:使用临时变量,比如temp_num1, temp_num2
//4.变量名不够清晰,太多temp temp_等诸如此类含糊不清的名字,不便于维护
//改进思路:以后逐渐养成习惯再说
//5.太习惯用//就不会用/**/了
//改进思路:下次一定
//6.不行了,除法还是有问题。我放弃了。。以后再说吧。我尽力了。
//7.不行,我又来了。继续战斗。
//8.不行了。写的太长了,AI已经说不清楚了。我决定放弃。日后再战。
//9.诶,我要是先写高精度除以低精度,再写高精度除以高精度呢,或许能逻辑更清晰一些(


#include <stdio.h>
#include <string.h>

int cmp(int num1[], int num2[], int LEN);
void add(int num1[], int num2[], int result[], int LEN);
int sub(int num1[], int num2[], int result[], int LEN);
void mul(int num1[], int num2[], int result[], int LEN);
void div(int num1[], int num2[], int result[], int remainder[], int LEN);


//main函数是输入数字ab,输出加减乘除
int main(void)
{
//输入
int a, b;
scanf("%d %d", &a, &b);

//数字a, b转换成数组num1[], num2[]
//目前写的是最大1000位
int num1[1000] = {0};
int num2[1000] = {0};
int i = 0, j = 0;
while(a > 0)
{
num1[i] = a % 10;
a /= 10;
i++;
}
while(b > 0)
{
num2[j] = b % 10;
b /= 10;
j++;
}

//结果初始化
int result1[1000] = {0};
int result2[1000] = {0};
int result3[1000] = {0};
int result4[1000] = {0};
int remainder[1000] = {0}; //余数,仅除法用



//运算
//想用哪个,就取消哪个的注释,就好了
//add(num1, num2, result1, 1000);
//int judge = sub(num1, num2, result2, 1000);
//mul(num1, num2, result3, 1000);
div(num1, num2, result4, remainder, 1000);




//计算结果总位数len
//记得改result几
int len;
for(len = 999; len>=0; len--)
{
if(result4[len] != 0)
{
break;
}
}
len++;




//输出结果
//记得改result几




//以下仅加法乘法适用
/*
for(int j = len-1; j>=0; j--)
{
printf("%d", result4[j]);
}
*/




//以下仅除法适用
//len_是余数remainder总位数
int len_;
for(len_ = 999; len_>=0; len_--)
{
if(result4[len_] != 0)
{
break;
}
}
len_++;

for(int j = len-1; j>=0; j--)
{
printf("%d", result4[j]);
}

printf("\n");

for(int j = len_-1; j>=0; j--)
{
printf("%d", remainder[j]);
}





//以下仅减法适用
/*
switch(judge)
{
case 0:
printf("0");
break;
case -1:
printf("-");
case 1:
for(int j = len-1; j>=0; j--)
{
printf("%d", result2[j]);
}
}
*/


return 0;
}


//比较num1[LEN]和num2[LEN]大小
//仅限于这里高精度比大小,因为是逆序排列的
//num1 = num2返回0
//num1 > num2返回1
//num1 < num2返回-1
int cmp(int num1[], int num2[], int LEN)
{
//i是num1最高位索引
int i = LEN-1; //LEN位的num1,最大的索引是num1[LEN-1]
for(; i >= 0; i--)
{
if(num1[i] != 0)
{
break;
}
}
//j是num2最高位索引
int j = LEN-1; //LEN位的num1,最大的索引是num1[LEN-1]
for(; j >= 0; j--)
{
if(num2[j] != 0)
{
break;
}
}
//max是i和j较大者,也就是需要比较大小的最高位
int max = (i > j ? i : j);

//我觉得no是序号的意思
for(int no = max; no >= 0; no--)
{
if(num1[no] > num2[no])
{
return 1;
}
else if(num1[no] < num2[no])
{
return -1;
}
}
return 0;
}


//最多能处理LEN位数
//传入的num1和num2, [0]是个位数,[1]是十位数
//result是一个数组, [0]是个位数,[1]是十位数
void add(int num1[], int num2[], int result[], int LEN)
{
//num1 + num2 = result

for(int i = 0; i < LEN; i++)
{
result[i] = num1[i] + num2[i];
}

//处理进位
for(int j = 0; j <= LEN-1; j++) //后续涉及到result[j+1],所以要j <= LEN-1,避免数组越界
{
result[j+1] += result[j] / 10;
result[j] %= 10;
}
}


//最多能处理LEN位数,但结果result也要是少于LEN位数!!!
//若result为负数,则应少于LEN-1位数!!!
//传入的num1和num2, [0]是个位数,[1]是十位数
//result是一个数组, [0]是个位数,[1]是十位数
//result = 0则return 0
//result > 0则return 1
//result < 0则return -1
//本函数只传回绝对值结果result,请在函数外负责负号'-'的打印
int sub(int num1[], int num2[], int result[], int LEN)
{
//result = |num1 - num2|

if(cmp(num1, num2, LEN) == 0)
{
memset(result, 0, LEN * sizeof(int));
return 0;
}

int judge = 0;
//检查是否num1 >= num2
//如果num1 < num2则使num1和num2互换。judge记为1,一会输出负数
if(cmp(num1, num2, LEN) < 0)
{
judge = 1;
int temp[LEN] = {0};
memcpy(temp, num1, LEN*sizeof(int));
memcpy(num1, num2, LEN*sizeof(int));
memcpy(num2, temp, LEN*sizeof(int));
}

//计算result的绝对值
for(int i = 0; i <= (LEN-1-1); i++) //result数组初始化只到了LEN,所以最多只能到result[LEN-1]
{
if(num1[i] >= num2[i])
{
result[i] = num1[i] - num2[i];
}
else
{
result[i] = num1[i] + 10 - num2[i];
num1[i+1] -= 1; //这里涉及到了num1[i+1],所以i+1 <= LEN-1即i <= LEN-2
}
}

if(judge == 0)
{
return 1;
}
else
{
return -1;
}
}


//最多能处理LEN位数,但结果result也要是少于LEN位数!!!
//传入的num1和num2,[0]是个位数,[1]是十位数
//result是一个数组,[0]是个位数,[1]是十位数
void mul(int num1[], int num2[], int result[], int LEN)
{
//num1 * num2 = result

for(int i = 0; i < LEN ;i++)
{
for(int j = 0; j < LEN; j++)
{
result[i + j] += num1[i] * num2[j];
}
}

for(int j = 0; j < LEN-1; j++)
{
result[j+1] += result[j] / 10;
result[j] %= 10;
}
}


void div(int num1[], int num2[], int result[], int remainder[], int LEN)
{
//num1 ÷ num2 = result ……remainder

//num1 = num2则商1余0
if(cmp(num1, num2, LEN)==0)
{
memset(result, 0, LEN * sizeof(int));
result[0] = 1; //这行能行吗
memset(remainder, 0, LEN * sizeof(int));
return; //return能行吗
}

//num1 < num2则商0余num1
if(cmp(num1, num2, LEN) < 0)
{
memset(result, 0, LEN * sizeof(int));
memcpy(remainder, num1, LEN * sizeof(int));
return; //return能行吗
}

//num1 > num2则正常除
//i是num1最高位索引,num1一共有i+1位
//求i
int i = LEN-1; //最多LEN位的num1,最大的索引是num1[LEN-1]
for(; i >= 0; i--)
{
if(num1[i] != 0)
{
break;
}
}
//j是num2最高位索引,num2一共有j+1位
//求j
int j = LEN-1; //LEN位的num1,最大的索引是num1[LEN-1]
for(; j >= 0; j--)
{
if(num2[j] != 0)
{
break;
}
}

//初始化curr,一会用curr进行试商
int curr[LEN] = {0};
for(int count = i-j; count < LEN; count++) //把count换为正在试商的相关部分
{
curr[count - (i - j)] = num1[count];
}

//计算商 m * num2移位,num1 -= m * num2
for(int k = i-j; k >= 0; k--)
{
int m = 0;
for(; m <= 10; m++) //试商m,从0到9
{
int m_[LEN] = {0}; //把m换成数组形式的m_
m_[0] = m;

int mul_[LEN] = {0}; //如果mul_ = m_ * num2 > curr说明已经超了,正确商是m-1
mul(m_, num2, mul_, LEN);

if((cmp(mul_, curr, LEN) > 0) && (m != 0))
{
m -= 1;
break;
}
else if((cmp(mul_, curr, LEN) > 0) && (m == 0))
{
break;
}
}
result[k] = m; //试商结束

int m_[LEN] = {0};
m_[0] = m;
int mul_[LEN] = {0};
mul(m_, num2, mul_, LEN);
int temp[LEN] = {0}; //curr = curr - num2 * m_ (temp = curr - mul_再curr=temp) if(k!= 0) 再 * 10 + 下一位[k-1]
sub(curr, mul_, temp, LEN);
memcpy(curr, temp, LEN * sizeof(int));

if(k != 0)
{
for(int no_name = LEN-k; no_name > 0; no_name--) //给变量起名太难了。。。
{
curr[no_name] = curr[no_name-1];
}
curr[0] = num1[k-1];
}
}

//计算余数 //remainder = num1 - num2 * result //我靠,AI提示说,最后的curr就是余数
/*
int mul_[LEN] = {0};
mul(num2, result, mul_, LEN);
sub(num1, mul_, remainder, LEN);
*/
memcpy(remainder, curr, LEN * sizeof(int));
}


//这里什么也没有,你在找什么 :p

2026.2.13
这两天我突然想起来了这个东西,于是便有了修修除法函数的想法
今天我正式动手,发现当初自己写的东西是真难看啊……
看来写出便于维护的代码,真的是个手艺活

在我把代码复制到vs code的时候,报了一堆错误,吓我一跳
原来是当初用了变长数组,写了int temp[LEN] = {0};,然后就报错了
我问了一下AI,思考了一下,改成了int temp[1000] = {0},再编译就没有报错了
其实我觉得这里可以这么写:把代码中所有的1000换成LEN,然后在最前边写上宏定义#define LEN 1000


在原来的代码中,开头的部分我列举了很多问题和可能的解决方案
其实那些问题我基本都没有解决(
今天我只是来尝试好好写一下除法的函数而已


我看着看着自己当初写的代码,其实我也发现,当初的注释写的还是挺好的,非常全


vs code的“替换”功能和自动补全是真好用啊
看我tab tab tab 😋


被自己气笑了
我把div函数从头看到尾,一边感叹自己写的真妙,一边感叹自己写的真是不便于维护
看完了div函数,我也没有改任何的逻辑
那当初是哪里错了呢?
回头一看,是main函数的remainder的长度len_的计算,写错了。写成result4的长度了

算了,反正也看了一遍div函数,也改了变量名字。我就把div函数贴在这里吧

哦对了,我应该处理一下特殊情况

如果是num1 == num2的话,remainder是0
如果是num1 < num2的话,result4是0
我感觉这个对于div函数没什么问题,但应该改一改main对于除法输出的逻辑,比如判断remainder 是否和全零数组zerocmp值为0之类的。这里我就不贴main函数了

如果num2==0,我发现result4会莫名其妙出现一堆1
至于出现1的原因,我问了一下AI。说是在我的代码中,如果num2==0会导致每一位商都变为11,再打印出来便成了一堆1

我把div函数改成了返回int的函数,如果返回-1则说明num2==0

哦还有,AI提醒我说应该在div函数里检查一下总位数是否超出LEN。这个就暂时先不改了,再说吧。


下面是我改过的高精度除法函数(被除数和除数都是高精度)

点击这里查看代码
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
//需要num1和num2都是正整数
//最多能处理LEN位数,但结果result也要是少于LEN位数!!!
//如果返回值为-1,说明num2==0
int div(int num1[], int num2[], int result[], int remainder[], int LEN)
{
//num1 ÷ num2 = result ……remainder


//先判断是否为0
//如果num2==0则返回-1,发生除零错误
int zero[1000] = {0};
if(cmp(num2, zero, LEN) == 0){
return -1;
}


//num1 = num2则商1余0
if(cmp(num1, num2, LEN)==0)
{
memset(result, 0, LEN * sizeof(int));
result[0] = 1; //这行能行吗 //更新:能行的
memset(remainder, 0, LEN * sizeof(int));
return 0; //return能行吗 //更新:void函数是可以return的(只不过现在我改成int函数了
}

//num1 < num2则商0余num1
if(cmp(num1, num2, LEN) < 0)
{
memset(result, 0, LEN * sizeof(int));
memcpy(remainder, num1, LEN * sizeof(int));
return 0; //return能行吗 //更新:void函数是可以return的(只不过现在我改成int函数了
}


//num1 > num2则正常除


//len_num1是num1总位数
//求len_num2
int len_num1 = LEN-1; //最多LEN位的num1,最大的索引是num1[LEN-1]
for(; len_num1 >= 0; len_num1--)
{
if(num1[len_num1] != 0)
{
break;
}
}
len_num1++;

//len_num2是num2总位数
//求len_num2
int len_num2 = LEN-1; //LEN位的num1,最大的索引是num1[LEN-1]
for(; len_num2 >= 0; len_num2--)
{
if(num2[len_num2] != 0)
{
break;
}
}
len_num2++;

//初始化curr,一会用curr进行试商
int curr[1000] = {0};
for(int count = len_num1-len_num2; count < LEN; count++) //把count换为正在试商的相关部分
{
curr[count - (len_num1 - len_num2)] = num1[count];//curr下标从0到LEN-1-(len_num1-len_num2) //num1下标从len_num1-len_num2到LEN-1
}
//欸,既然这里这么难以理解,那我加个ASCII art吧😋
/*
|<-------------------->| 这是LEN
num1 7 6 5 3 2 这是num1
num2 2 5 这是num2 (进行除法竖式计算时,左对齐,因为一会要进行试商)
|<->| 这是len_num1 - len_num2
|<------------->| 这是curr的初始位置

^
|
|
这里是最高的位
也就是num1和num2下标为LEN-1的位置
*/

//计算商 m * num2移位,num1 -= m * num2
for(int k = len_num1-len_num2; k >= 0; k--) //k是curr个位对于num1和num2而言的下标
{
int m = 0;
for(; m <= 10; m++) //试商m,从0到9
{ //这里m<=10是为了兼容m=9的情况
int m_arr[1000] = {0}; //把m换成数组形式的m_arr,以便于使用刚刚写的函数cmp, mul等
m_arr[0] = m;

int mul_result[1000] = {0}; //如果mul_result = m_arr * num2 > curr说明已经超了,正确商是m-1
mul(m_arr, num2, mul_result, LEN); //并且这兼容m=0的情况

if((cmp(mul_result, curr, LEN) > 0) && (m != 0))
{
m -= 1;
break;
}
}
result[k] = m; //试商结束

int m_arr[1000] = {0};
m_arr[0] = m;
int mul_result[1000] = {0};
mul(m_arr, num2, mul_result, LEN);

int temp[1000] = {0}; //首先把curr减去mul_result,一会再下一步
sub(curr, mul_result, temp, LEN); //curr = curr - mul_result (temp = curr - mul_result再curr=temp)
memcpy(curr, temp, LEN * sizeof(int));

if(k != 0) //if(k!= 0) 就给curr * 10 + 下一位[k-1]
{
for(int no_name = LEN-k; no_name > 0; no_name--) //给变量起名太难了。。。
{
curr[no_name] = curr[no_name-1]; //更新时的补充:这个注释和变量名挺有意思啊,我要留着哈哈
} //no_name是curr的下标,这里是把curr整体左移一位,即curr * 10
curr[0] = num1[k-1]; //而这一行则是加上了下一位的num1[k-1]
}
}

//计算余数 //remainder = num1 - num2 * result //我靠,AI提示说,最后的curr就是余数
/* //更新时的补充:这个是真妙啊
int num2_mul_result[1000] = {0};
mul(num2, result, num2_mul_result, LEN);
sub(num1, num2_mul_result, remainder, LEN);
*/
memcpy(remainder, curr, LEN * sizeof(int));
}

//这里什么也没有,你在找什么 :p