高精度实数计算(链表实现)

        我们都知道加法,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"

  • 0
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值