我们都知道加法,1+1=2.c语言实现一个加法也是初学者的必修课。但是你有没有想过如果两个超过long long 类型的数据在c语言中要怎么实现加减乘除?比如一个一百位的数加上另一个一百位的数。好接下来先介绍高精度算法的处理方式。大家如果有发现什么BUG请私信告诉我谢谢。(我现在也不能保证我的屎山代码是正确的hh)
警告:使用链表处理需要保持数据结构的高度一致!!!(不然就会像我一样一大堆的段错误)
处理方式
既然不能直接用整形进行处理,那么我们就用长度不限的数组来进行处理,但是这个数组比较特别,它的元素代表它在一个数的各位。比如:
大数:123456789101112131415161718
数组:{1,2,3,4,5,6,7,8,9,1,0,1,1,1,2,1,3,1,4,1,5,1,6,1,7,1,8}
接下来的高精度操作都是这样处理的。(我的链表里存的都不是ASCII都是整形数字,所以输出也用的%d)
对链表的此链表的规定
因为我想用单链表加单结构体解决掉高精度计算,所以在如何储存大数的长度的时候犯了难。但是看到头节点还没有好好利用起来时,我把大数的位数存进了头节点的数据域,在后面讲减法的时候会用到这个,快速比较两个数的大小关系。这里先放一个结构体的定义。因为在高精度乘法里有一个权值的思想,所以我规定结构体里的 id 代表各位数的权值,后面会讲到 id 有什么用。
在除法失败之后我决定放弃低效的单链表,转向了双链表的实现。一下是新节点和头节点的定义。
1——> 高精度加法
思想
让我们想想自己是怎么计算两个数的和的,比如9876987+999999。我们先会把他们对齐对吧?按地位对齐。
然后再各位先加,逢10进1,在计算机里我们仿照人对加法的操作。首先解决第一个问题--->按低位对齐。然后是第二个问题,逢十进一。
按低位对齐
我们这里用链表实现,所以之后的所有语言都是基于链表的。如果链表不熟悉的小伙伴可以先看看链表专题。(传送门)
初学者的链表总结_zhywyt的博客-CSDN博客链表c语言实现https://blog.csdn.net/weixin_73620289/article/details/127691181?spm=1001.2014.3001.5502如果一个数在存进链表的时候是倒序存入的,那么链表的第一个元素就是这个大数的最低位对。所以我们在处理大数的时候把他们都用倒序储存。倒序储存很简单,只需要写一个insert函数就行。这里我的insert函数考虑到插入后头节点里的位数信息需要更新,那么每次创建头节点时必须给它 的data初始化,不然在调用insert的时候就会出现段错误。
这里我们需要处理实数,所以在处理小数部分的时候,我们在小数部分的短的数后面加上零,使得两个数的小数部分的长度相等。
逢十进一
在我们把两个数的个位加起来之后,需要对各位的数字进行处理,从低位开始,逢十进一。这个操作也很简单,只需要用一个变量temp来保存进位就行。初始化temp位0表示没有进位。
图中的 at , bt 表示本次循环中 a 的数字大小,bt 同理。因为两个数的长度很可能不一样,也就是说在短的数遍历完之后,长的数还有元素需要加到结果里,这时候需要设置短的那个大数的本次循环数字大小为零。
两个数相加,得到的结果长度最大是max(lena,lenb)+1,最小是max(lena,lenb)所以我们只需要先循环max(lena,lenb)次,为结果插入元素,然后在循环外面判断是否有进位,进位的数由整型data保存,于是有:
细节处理
我们从逆序输入加数,然后逆序插入到结果中,那么结果其实已经是正序了,设想一下,如果我们要连加的话,那么输入的格式和输出的格式一定不能是相反的,所以我们最后把得到的链表reverse一下,得到逆序的链表然后返回。还有就是对数据的处理方面,输入的大数需要去除前导零。注意!去除前导零的函数要记得让头节点的 data 减小相应的大小。
代码展示
head* add(head* a, head* b) {
if (a->id == -1 && b->id == 1) {a->id = 1; return mins(b, a);}
else if (a->id == 1 && b->id == -1) {b->id = 1; return mins(a, b);}
head* ans = NewHead();
if (a->id == -1 && b->id == -1)ans->id = -1;
else ans->id = 1;
if (a->sgn < b->sgn) { head* q = a; a = b; b = q; }
ans->sgn = a->sgn;
for (int i = 0; i < a->sgn-b->sgn; i++) {insert(b, 0);}
a->sgn = b->sgn;
if (a->pos < b->pos) { head*q = a; a = b; b = q; }
int ad, bd, temp=0;
node* ap = a->head, * bp = b->head;
for (int i = 0; i < a->digits; i++) {
if (ap) {ad = ap->data;ap = ap->next;}
else ad = 0;
if (bp) { bd = bp->data; bp = bp->next; }
else bd = 0;
temp += ad + bd;
append(ans, temp % 10);
temp /= 10;
}
ans->pos = a->pos;
if (temp)append(ans, 1), ans->pos++;
return ans;
}
2——> 高精度减法
思想
和加法不同,如果说加法是在进位的话,减法就是在退位了。我们这里只想处理a>b的情况。所以在减之前,先判断 a 和 b 的大小关系,如果 b 大于 a 那么就交换 a 和 b 并且给ans添上负的记号。也就是ans的头节点里的 id 了。id 为-1表示这个数是负数,我们在输出的时候判断头节点里的记号是否是-1,如果是的话先输出一个负号。减法和加法在操作上是差不多的,只不过减法是判断得到的数是不是负数,如果是负数的话就借十。从高位借十。这就是高位借十的操作。
比较函数compare()
写一个比较函数,来判断 a 和 b 谁大。首先比较 a 和 b 的长度关系,这里就用到之前存在头节点里的pos了,pos是整数部分的长度,直接决定了两个数的大小关系。当长度不一样的时候,可以直接判断出大小关系。当长度一样的时候,从高位开始比较,直到出现不一样的或者到达最后。
int compare(head* a, head* b) {
if (a->pos != b->pos)return a->pos - b->pos;
node* ap = a->tail, * bp = b->tail;
while (ap->data == bp->data&&ap&&bp) {
ap = ap->last,bp=bp->last;
}
if (ap && bp) return ap->data - bp->data;
else if (ap)return 1;
else if (bp)return -1;
else return 0;
}
细节处理
减法得到的结果是有可能出现前导零的,所以在返回答案之前需要去除前导零。
代码展示
head* mins(head* a, head* b) {
head*ans = NewHead();
if (a->id == -1 && b->id == 1 || a->id == 1 && b->id == -1) {
b->id *= -1;
return add(a, b);
}
else if (a->id == -1 && b->id == -1) {
a->id = 1, b->id = 1;
mins(b, a);
}
ans->id = 1;
int flag = compare(a, b);
if (flag < 0) { head* p = a; a = b; b = p; ans->id = -1; }
else if (flag == 0) {insert(ans, 0);return ans; }
if (a->sgn < b->sgn) { head* q = a; a = b; b = q; }
ans->sgn = a->sgn;
for (int i = 0; i < a->sgn - b->sgn; i++) { insert(b, 0); }
a->sgn = b->sgn;
if (a->pos < b->pos) { head* q = a; a = b; b = q; }
int ad, bd, temp = 0;
node* ap = a->head, * bp = b->head;
for (int i = 0; i < a->digits; i++) {
if (ap) { ad = ap->data; ap = ap->next; }
else ad = 0;
if (bp) { bd = bp->data; bp = bp->next; }
else bd = 0;
temp += ad - bd;
if (temp < 0) { temp += 10; append(ans, temp % 10); temp = -1; }
else append(ans, temp),temp=0;
}
ans->pos = a->pos,ans->sgn=ans->digits-ans->pos;
while (ans->sgn > 0 && !ans->head->data) { ap = ans->head; bp = ans->head->next; ans->head->next->last = NULL; free(ap); ans->head = bp; ans->sgn--; ans->digits--; }
while (ans->pos > 1 && !ans->tail->data) { ap = ans->tail; bp = ans->tail->last; ans->tail->last->next = NULL; free(ap); ans->tail = bp; ans->pos--; ans->digits--; }
return ans;
}
3——>高精度乘法(十进制)
思想
乘法也是遵循我们对两个数竖式相乘的步骤, a , b 两个数比如12345 和 6789看其中一个数,12345中第一位1它的权值是4 也就是看成1*10e4, 第二位数2 看成2*10e3,后面的同理。再看6789=6*10e3+7*10e2+8*10e1+9*10e0。先给数加上权值,然后再进行乘法操作。对 a 中的每一位数都需要和 b 中的每一位数进行乘法操作。得到的数放在 ans 的 a->id+b->id中。我们需要一个find函数,来找到ans 中对应权值的所在位置。为了减少find 函数的使用,我们借助循环的线性性质,对于 a 中的每一位数,我们遍历 b 这个过程对应 ans 中的权值就是连续的,我们只需要应用好这个线性性,写乘法可能比加法还简单。
代码展示
head* multi(head* a, head* b) {
head* ans = NewHead();
ans->id = 1;
ans->id = (a->id * b->id);
ans->sgn = a->sgn + b->sgn;ans->pos = a->pos + b->pos;
for (int i = 0; i < ans->pos+ans->sgn; i++)insert(ans, 0);
node* ap = a->head, * p = ans->head;
for (int i = 0; i < a->digits; i++) {
node* q = p, * bp = b->head;
for (int j = 0; j < b->digits; j++) {
q->data += ap->data * bp->data;
bp = bp->next,q=q->next;
}
p = p->next, ap = ap->next;
}
ap = ans->head;
while (ap->next) {
if (ap->data > 10) {
int temp = ap->data / 10;
ap->data %= 10;
ap->next->data += temp;
}
ap = ap->next;
}
node * bp;
while (ans->sgn > 0 && !ans->head->data) { ap = ans->head; bp = ans->head->next; ans->head->next->last = NULL; free(ap); ans->head = bp; ans->sgn--; ans->digits--; }
while (ans->pos > 1 && !ans->tail->data) { ap = ans->tail; bp = ans->tail->last; ans->tail->last->next = NULL; free(ap); ans->tail = bp; ans->pos--; ans->digits--; }
return ans;
}
4——>高精度除法
思想
想象一下竖式计算,比如 16 / 2 我们的做法是,先从较大的那个数的高位取一位数,这里是 1 ,然后比较 1 和 2 的大小,发现 1 小了,那么给答案链表加一个 0,再在16 里取下一位,得到 16和 2 比较,发现比 2 大,就用 16减去 2,得到 14 存下来,答案链表中的该位加一,重复这个操作,直到 16 变成小于 2.最后再去除答案链表中的前导零,就结束操作了。
注:这些图片来组b站up主SherlockGn,她对高精度的讲解非常到位,大家也可以去看他的视频。高精度计算除法,计算大数相除、求余再也不用求人了!_哔哩哔哩_bilibili
一个视频带你了解高精度计算!再也不用担心数据溢出了!_哔哩哔哩_bilibili
开始拓展:我们现在要计算的不只是整数的除法,我要算到小数位,那么在除法函数中额外添加一个精度参数 int n;n就是保留的小数点位数(我这里只是实现向下取整,如果需要四舍五入请再计算一位,然后加五除十。)我们人脑做除法之前一般要把除数升到整数对吧?也是为了方便计算,我们计算除法的时候也这么实现。
while (b->sgn) {//b补位成整数
b->sgn--, b->pos++;
if (a->sgn) { a->sgn--, a->pos++; }
else { insert(a, 0), a->pos++; }
}
设想一下,如果我们需要20位小数,那么在竖式计算到被除数的位数不够的时候我们怎么办?——补零,是的,我们实现也是通过补零的方法。那我们需要补多少零呢?只要a有一位小数,那我们的精度就是a的小数位,所以只需要补上n-a->sgn个零就行了。
int sgn = a->sgn;//千万注意要记录a->sgn的值,不然会出现补一半零的情况
if (a->sgn < n)for (int i = 0; i < n - sgn; i++) { insert(a, 0), a->sgn++; }
我们就得到了两个类整数,然后实行整数的除法就行了,只是在除的过程中需要注意一些细节。除法的代码我就不单独挂了,可以去下面的合集查看。如果程序有什么地方有算法错误的话,请务必联系我。我这份代码并没有对输入进行严格的判断,所以输入需要按照规则来。输入不规则导致的报错请勿找我。
代码合集
最后是我的屎山代码。(建议在VS2022下运行)
#include <stdio.h>
#include <stdlib.h>
struct node {
node* next;
node* last;
int data;
};
typedef struct node node;
struct head {
node* head;//头指针
node* tail;//尾指针
int digits;//总位数
int pos;//整数位
int sgn;//小数位
int id;//数的正负//未初始化Read后初始化了
};
typedef struct head head;
//不会让除digits以外的值变化,在head插入
void insert(head* la, int data);
//不会让除digits以外的值变化,在tail插入
void append(head* la, int data);
node* NewNode();
void print(head* la);
void destroy(head* la);
char ReadNumber(head* la);
head* NewHead();
void run();
int compare(head* a, head* b);
head* add(head* a, head* b);
head* mins(head* a, head* b);
head* multi(head* a, head* b);
head* divide(head* a, head* b, int jingdu);
int main() {
run();
return 0;
}
void insert(head* la, int data) {
node* p = NewNode();
p->data = data;
if (!la->tail) {
la->tail = p;
p->next = NULL;
p->last = NULL;
}
else {
p->last = la->head->last;
p->next = la->head;
la->head->last = p;
}
la->head = p;
la->digits++;
}
void append(head* la, int data) {
node* p = NewNode();
p->data = data;
if (!la->tail) {
la->head = p;
p->next = NULL;
p->last = NULL;
}
else {
p->last = la->tail;
p->next = la->tail->next;
la->tail->next = p;
}
la->tail = p;
la->digits++;
}
node* NewNode() {
node* p = (node*)malloc(sizeof(node));
if (!p) {
printf("error!\n");
exit(-1);
}
return p;
}
head* NewHead() {
head* la = (head*)malloc(sizeof(head));
if (!la) {
printf("error!\n");
exit(-1);
}
la->tail = NULL, la->head = NULL, la->digits = 0;
return la;
}
void print(head* la) {
node* p = la->tail;
int cnt = 1;
if (la->id == -1)printf("-");
while (p) {
if ((cnt == la->pos) && p->last) {
printf("%d.", p->data);
cnt++;
}
else {
cnt++;
printf("%d", p->data);
}
p = p->last;
}
}
void destroy(head* la) {
node* p = la->head;
while (p) {
node* q = p;
p = p->next;
free(q);
}
free(la);
}
char ReadNumber(head* la) {
char c = getchar();
la->id = 1, la->pos = 0;
if (c == '-')la->id = -1;
else insert(la, c - '0');
while ((c = getchar()) >= '0' && c <= '9' || c == '.') {
if (c == '.') {
la->pos = la->digits;
}
else {
insert(la, c - '0');
}
}
if (!la->pos)la->pos = la->digits;
la->sgn = la->digits - la->pos;
return c;
}
int compare(head* a, head* b) {
if (a->pos != b->pos)return a->pos - b->pos;
node* ap = a->tail, * bp = b->tail;
while (ap && bp && ap->data == bp->data) {
ap = ap->last, bp = bp->last;
}
if (ap && bp) return ap->data - bp->data;
else if (ap)return 1;
else if (bp)return -1;
else return 0;
}
void run() {
while (1) {
head* a = NewHead(), * b = NewHead(), * ans = NewHead();
char c;
c = ReadNumber(a), ReadNumber(b);
if (c == '+') {
print(a), printf(" + "), print(b), printf(" = ");
ans = add(a, b);
}
else if (c == '-') {
print(a), printf(" - "), print(b), printf(" = ");
ans = mins(a, b);
}
else if (c == '*') {
print(a), printf(" * "), print(b), printf(" = ");
ans = multi(a, b);
}
else if (c == '/') {
int n;
printf("\nPlaese input the jingdu:\n"), scanf("%d", &n);
print(a), printf(" / "), print(b), printf(" = ");
ans = divide(a, b, n);
}
print(ans);
printf("\n");
destroy(a), destroy(b), destroy(ans);
}
}
head* add(head* a, head* b) {
if (a->id == -1 && b->id == 1) { a->id = 1; return mins(b, a); }
else if (a->id == 1 && b->id == -1) { b->id = 1; return mins(a, b); }
head* ans = NewHead();
if (a->id == -1 && b->id == -1)ans->id = -1;
else ans->id = 1;
if (a->sgn < b->sgn) { head* q = a; a = b; b = q; }
ans->sgn = a->sgn;
for (int i = 0; i < a->sgn - b->sgn; i++) { insert(b, 0); }
a->sgn = b->sgn;
if (a->pos < b->pos) { head* q = a; a = b; b = q; }
int ad, bd, temp = 0;
node* ap = a->head, * bp = b->head;
for (int i = 0; i < a->digits; i++) {
if (ap) { ad = ap->data; ap = ap->next; }
else ad = 0;
if (bp) { bd = bp->data; bp = bp->next; }
else bd = 0;
temp += ad + bd;
append(ans, temp % 10);
temp /= 10;
}
ans->pos = a->pos;
if (temp)append(ans, 1), ans->pos++;
return ans;
}
head* mins(head* a, head* b) {
head* ans = NewHead();
if (a->id == -1 && b->id == 1 || a->id == 1 && b->id == -1) {
b->id *= -1;
return add(a, b);
}
else if (a->id == -1 && b->id == -1) {
a->id = 1, b->id = 1;
mins(b, a);
}
ans->id = 1;
int flag = compare(a, b);
if (flag < 0) { head* p = a; a = b; b = p; ans->id = -1; }
else if (flag == 0) { insert(ans, 0); return ans; }
if (a->sgn < b->sgn) { head* q = a; a = b; b = q; }
ans->sgn = a->sgn;
for (int i = 0; i < a->sgn - b->sgn; i++) { insert(b, 0); }
a->sgn = b->sgn;
if (a->pos < b->pos) { head* q = a; a = b; b = q; }
int ad, bd, temp = 0;
node* ap = a->head, * bp = b->head;
for (int i = 0; i < a->digits; i++) {
if (ap) { ad = ap->data; ap = ap->next; }
else ad = 0;
if (bp) { bd = bp->data; bp = bp->next; }
else bd = 0;
temp += ad - bd;
if (temp < 0) { temp += 10; append(ans, temp % 10); temp = -1; }
else append(ans, temp), temp = 0;
}
ans->pos = a->pos, ans->sgn = ans->digits - ans->pos;
while (ans->sgn > 0 && !ans->head->data) { ap = ans->head; bp = ans->head->next; ans->head->next->last = NULL; free(ap); ans->head = bp; ans->sgn--; ans->digits--; }
while (ans->pos > 1 && !ans->tail->data) { ap = ans->tail; bp = ans->tail->last; ans->tail->last->next = NULL; free(ap); ans->tail = bp; ans->pos--; ans->digits--; }
return ans;
}
head* multi(head* a, head* b) {
head* ans = NewHead();
ans->id = 1;
ans->id = (a->id * b->id);
ans->sgn = a->sgn + b->sgn; ans->pos = a->pos + b->pos;
for (int i = 0; i < ans->pos + ans->sgn; i++)insert(ans, 0);
node* ap = a->head, * p = ans->head;
for (int i = 0; i < a->digits; i++) {
node* q = p, * bp = b->head;
for (int j = 0; j < b->digits; j++) {
q->data += ap->data * bp->data;
bp = bp->next, q = q->next;
}
p = p->next, ap = ap->next;
}
ap = ans->head;
while (ap->next) {
if (ap->data > 10) {
int temp = ap->data / 10;
ap->data %= 10;
ap->next->data += temp;
}
ap = ap->next;
}
node* bp;
while (ans->sgn > 0 && !ans->head->data) { ap = ans->head; bp = ans->head->next; ans->head->next->last = NULL; free(ap); ans->head = bp; ans->sgn--; ans->digits--; }
while (ans->pos > 1 && !ans->tail->data) { ap = ans->tail; bp = ans->tail->last; ans->tail->last->next = NULL; free(ap); ans->tail = bp; ans->pos--; ans->digits--; }
return ans;
}
head* divide(head* a, head* b, int n) {
head* ans = NewHead(), * leave = NewHead();
ans->id = (a->id * b->id), ans->pos = 0, ans->sgn = 0, leave->pos = 0, leave->sgn = 0, leave->id = 1;
a->id = 1, b->id = 1;
//先鸽着a
if (a->digits == 1 && a->head->data == 0) { insert(ans, 0), ans->sgn++; return ans; }
while (b->sgn) {//b补位成整数
b->sgn--, b->pos++;
if (a->sgn) { a->sgn--, a->pos++; }
else { insert(a, 0), a->pos++; }
}//添加小数位,保证精度
int sgn = a->sgn;
if (a->sgn < n)for (int i = 0; i < n - sgn; i++) { insert(a, 0), a->sgn++; }
node* ap = a->tail, * bp;
for (int i = 0; i < a->pos + n; i++, ap = ap->last) {
insert(ans, 0), insert(leave, ap->data), leave->pos++;
i >= a->pos ? ans->sgn++ : ans->pos++;
while (compare(leave, b) >= 0) {
leave = mins(leave, b);
ans->head->data++;
}
}
while (ans->sgn > 0 && !ans->head->data) { ap = ans->head; bp = ans->head->next; ans->head->next->last = NULL; free(ap); ans->head = bp; ans->sgn--; ans->digits--; }
while (ans->pos > 1 && !ans->tail->data) { ap = ans->tail; bp = ans->tail->last; ans->tail->last->next = NULL; free(ap); ans->tail = bp; ans->pos--; ans->digits--; }
return ans;
}
var code = "c04df991-3385-45fb-a5aa-472e4b25d6d5"