【C++】【算法】高精度加减乘除

0 引言

在用C++写代码时,为了解决各种问题,我们会经常使用到加减乘除,我们对加减乘除运算也再熟悉不过。

但是,C++作为一门强类型语言,不管是使用short,int,亦或是long long,对数据存储范围都是有限的; 

类型大小范围
short2字节-32768 ~ 32767
int4字节-2147483648~2147483647
long long8字节-2^63 ~ 2^63-1

当我们加减乘除要运算的数超过数据存储的范围时就无法进行运算,否则会导致越界造成严重的后果,那我们如果要进行这样高精度的计算,就需要使用到算法来对其进行实现,这种算法也可称为高精度算法,能实现高精度的运算;

1 高精度算法的介绍以及思路

当我们由于储存范围的原因无法使用C++的内置类型来进行运算时,我们可以借助STL库中的string来得到要计算的数据,再将string中的数据存入一个数组中,然后对数组中的每一位进行相应的运算,最后的结果存入数组中进行输出,我们的高精度算法也就完成了;

算法思路:

2 高精度加法

对于高精度加法,我们在处理完数据逆序存储于字符串中之后,对每一位进行加法运算,随后处理好进位,如下:

 我们先定义一个全局变量,当做数组的大小,该全局变用户可根据需要自行改变,确保其能存下我们需要计算的数据:

const int N = 1e6 + 10;

再定义三个全局数组,分别存储两个要计算的数据和结果

int a[N], b[N], c[N];

然后定义三个全局变量表示三个数据的长度

int la, lb, lc;

先看第一步:将string中要计算的数据,逆序存储于数组中

cin得到要相加的两个数据,并使用string自带的size功能得出三个数据的长度la,lb,lc,结果的长度是两个要加的数据中大的数据的长度,如果说相加到最高位时还有进位,那么lc的长度应为la+1,这种特殊情况后面会单独处理:

string x, y;
cin >> x >> y;

la = x.size(), lb = y.size(), lc = max(la, lb);

如果要让字符串中的数据逆序存储于数组中,即a[0] = x[la - 1 - 0] ; a[1] = x[la -1 - 1] ; a[2] = x[la -1 - 2];  可以发现,其下标的和正好为la - 1,因此,可以直接得出循环,实现逆序存储:

	for (int i = 0; i < la; ++i) a[i] = x[la - 1 - i] - '0';
	for (int i = 0; i < lb; ++i) b[i] = y[lb - 1 - i] - '0';

这里千万注意,字符串x,y中存储的是字符,当将其转移到数组中时应当减去字符0(‘ 0 ’);

随后正常执行算法,然后输出,得到基本框架:

	add(a, b, c);

	for (int i = lc - 1; i >= 0; --i) cout << c[i];
	cout << endl;

	return 0;

下面来实现add算法:

对于数组中的两个数据相加,如下图所示,假设这两个数是1001和9099:

代码实现:

for (int i = 0; i < lc; ++i)
{
	c[i] += a[i] + b[i];
	c[i + 1] = c[i] / 10;
	c[i] %= 10;
}

注意:循环中第一个语句一定是c[i] += a[i] + b[i] ,因为还要算上进位,而第二步即得到进位,进位存储于下一个下标;c[i]本身的值去掉进位后即为c[i] % 10;

在这一步之后还需要更新lc的值,如果在加到最高位之后仍然有进位,那么lc的值应该加1,便于后序输出:

if (c[lc]) ++lc;

由此得到完整的add算法(参数可写可不写,因为是全局的):

void add(int* a, int* b, int* c)
{
	for (int i = 0; i < lc; ++i)
	{
		c[i] += a[i] + b[i];
		c[i + 1] = c[i] / 10;
		c[i] %= 10;
	}

	if (c[lc]) ++lc;
}

完整代码如下:

#include <iostream>
#include <string>
using namespace std;

const int N = 1e6 + 10;

int a[N], b[N], c[N];

int la, lb, lc;

void add(int* a, int* b, int* c)
{
	for (int i = 0; i < lc; ++i)
	{
		c[i] += a[i] + b[i];
		c[i + 1] = c[i] / 10;
		c[i] %= 10;
	}

	if (c[lc]) ++lc;
}

int main()
{
	string x, y;
	cin >> x >> y;

	la = x.size(), lb = y.size(), lc = max(la, lb);
	for (int i = 0; i < la; ++i) a[i] = x[la - 1 - i] - '0';
	for (int i = 0; i < lb; ++i) b[i] = y[lb - 1 - i] - '0';

	add(a, b, c);

	for (int i = lc - 1; i >= 0; --i) cout << c[i];
	cout << endl;

	return 0;
}

测试:

3 高精度减法

高精度减法的基本框架和add基本一致,只是在算法的实现上有所区别,先写出其基本框架:

void del(int* a, int* b, int* c)
{
	
}

int main()
{
	string x, y;
	cin >> x >> y;

	la = x.size(), lb = y.size(), lc = max(la, lb);
	for (int i = 0; i < la; ++i) a[i] = x[la - 1 - i] - '0';
	for (int i = 0; i < lb; ++i) b[i] = y[lb - 1 - i] - '0';

	del(a, b, c);

	for (int i = lc - 1; i >= 0; --i) cout << c[i];
	cout << endl;

	return 0;
}

在此框架的基础上,在将string中的数据拷入数组之前,我们要求x > y,方便后序的减法操作,如果x < y的话,得到的结果应该是一个负数,要先输出符号,然后交换a, b的值;

因此,在进行del之前,应该先比较a,b的值,如下所示:

if (cmp(x, y))
{
	// x < y
	cout << '-';
	swap(x, y);
}

实现cmp:

x,y是两个字符串,我们知道两个字符串比较大小是按照字典序进行比较,也就是说,9会比666666这个字符串还要大,所以我们不能直接使用大小于号进行比较,我们应该先比较其长度,若长度,长度长的,这个数一定更大,长度若相同,则按照字典序进行比较:

bool cmp(string& x, string& y)
{
	if (x.size() != y.size()) return x.size() < y.size();
	return x < y;
}

若x比y小返回真;

再来对del进行实现,del和add有所不同的是,在del的for循环中,a[i]和b[i]要进行相减的运算,这个运算结果暂时存储于c[i]中,如果说c[i] 小于0,说明要向下一位进行借位,那么c[i+1]应当--,同时c[i] 应当 + 10,实现如下:

for (int i = 0; i < lc; ++i)
{
	c[i] += a[i] - b[i];
	if (c[i] < 0)
	{
		--c[i + 1];
		c[i] += 10;
	}
}

在进行了如上循环后,还有一种特殊情况,比如9999 - 9999,按照如上循环得到的c中的结果应该为0000,因为lc的长度没有改变,所以我们需要做进一步处理,在有效数字之前不能存在0,即要去掉前导0,但结果若为0000,还需保留一个0,所以lc的长度至少要为1,如下:

while (lc > 1 && c[lc - 1] == 0) --lc;

完整代码如下:

#include <iostream>
#include <string>
using namespace std;

const int N = 1e6 + 10;

int a[N], b[N], c[N];

int la, lb, lc;

void del(int* a, int* b, int* c)
{
	for (int i = 0; i < lc; ++i)
	{
		c[i] += a[i] - b[i];
		if (c[i] < 0)
		{
			--c[i + 1];
			c[i] += 10;
		}
	}

	while (lc > 1 && c[lc - 1] == 0) --lc;
}

bool cmp(string& x, string& y)
{
	if (x.size() != y.size()) return x.size() < y.size();
	return x < y;
}

int main()
{
	string x, y;
	cin >> x >> y;

	if (cmp(x, y))
	{
		// a < b
		cout << '-';
		swap(x, y);
	}

	la = x.size(), lb = y.size(), lc = max(la, lb);
	for (int i = 0; i < la; ++i) a[i] = x[la - 1 - i] - '0';
	for (int i = 0; i < lb; ++i) b[i] = y[lb - 1 - i] - '0';

	

	del(a, b, c);

	for (int i = lc - 1; i >= 0; --i) cout << c[i];
	cout << endl;

	return 0;
}

测试:

4 高精度乘法

高精度乘法的基本框架也是和add基本一致,只是在算法的实现上有所区别,先写出其基本框架:

void mul(int* a, int* b, int* c)
{

}

int main()
{
	string x, y;
	cin >> x >> y;

	la = x.size(), lb = y.size(), lc = la + lb;
	for (int i = 0; i < la; ++i) a[i] = x[la - 1 - i] - '0';
	for (int i = 0; i < lb; ++i) b[i] = y[lb - 1 - i] - '0';

	mul(a, b, c);

	for (int i = lc - 1; i >= 0; --i) cout << c[i];
	cout << endl;

	return 0;
}

这里需要注意的是,lc的大小不再是la和lb较大的那个值,而是la+lb,因为对于两个数相乘,积的长度不会超过两个数的位数之和;

对于高精度乘法,对两个数列竖式计算乘法,如果向我们一般列竖式的做法,整个过程会变得非常麻烦,我们应该采用另一种更简单的方法来列竖式计算———不进制列竖式乘法,即在列竖式时直接写出相乘的结果,在相加后再统一进制,对123×456这两个数进行演示:

对于a,b和c,有这样的规律 ->  a[ i ] * b[ j ]的值刚好是c[ i + j ]处值的叠加,因此,只需要两个for循环就能初步完成这个算法:

for (int i = 0; i < la; ++i)
{
	for (int j = 0; j < lb; ++j)
	{
		c[i + j] += a[i] * b[j];
	}
}

此时得到的c中存储的数据还没有进位,所以我们要对其中的数据进行进位:

	// 进位
	for (int i = 0; i < lc; ++i)
	{
		c[i + 1] += c[i] / 10;
		c[i] %= 10;
	}

同样的,如果是0*9999,得到的结果会是00000,我们还是要处理前导0;

while (lc > 1 && c[lc - 1] == 0) --lc;

完整代码如下所示:

#include <iostream>
#include <string>
using namespace std;

const int N = 1e6 + 10;

int a[N], b[N], c[N];

int la, lb, lc;


void mul(int* a, int* b, int* c)
{
	for (int i = 0; i < la; ++i)
	{
		for (int j = 0; j < lb; ++j)
		{
			c[i + j] += a[i] * b[j];
		}
	}

	// 进位
	for (int i = 0; i < lc; ++i)
	{
		c[i + 1] += c[i] / 10;
		c[i] %= 10;
	}

	while (lc > 1 && c[lc - 1] == 0) --lc;
}

int main()
{
	string x, y;
	cin >> x >> y;

	la = x.size(), lb = y.size(), lc = la + lb;
	for (int i = 0; i < la; ++i) a[i] = x[la - 1 - i] - '0';
	for (int i = 0; i < lb; ++i) b[i] = y[lb - 1 - i] - '0';

	mul(a, b, c);

	for (int i = lc - 1; i >= 0; --i) cout << c[i];
	cout << endl;

	return 0;
}

测试:

注意,此代码默认输入的两个数为正整数,如果需要计算负数,则在计算之前使用一下绝对值,根据同号为正异号为负的规则先输出符号再正常计算,比较简单,这里不多做演示;

5 高精度除法

对于高精度除法,我一般不研究高精度÷高精度,没什么意义,只需要研究高精度÷低精度足矣,所以其框架会和加减乘有所不同,这里只有被除数需要存储进一个数组,除数则不需要,除出来的结果可能很大,也需要存储进一个数组中;

先写出基本框架:

#include <iostream>
#include <string>
using namespace std;

const int N = 1e6 + 10;

int a[N], b, c[N];

int la, lc;

void sub(int* a, int b, int* c)
{
	
}

int main()
{
	string x;
	cin >> x >> b;

	la = x.size(), lc = la;
	for (int i = 0; i < la; ++i) a[i] = x[la - 1 - i] - '0';

	sub(a, b, c);

	for (int i = lc - 1; i >= 0; --i) cout << c[i];
	cout << endl;

	return 0;
}

被除数与结果的长度是一样的;

对于sub算法,我们要使用的方法是这样的,以1234 ÷ 45来示范:

sub初步实现如下:

为了使精度的容错更高,余数可能很大,所以我们将t定义为long long类型:

typedef long long LL;

void sub(int* a, int b, int* c)
{
	LL t = 0;
	for (int i = lc - 1; i >= 0; --i)
	{
		t = t * 10 + a[i];
		c[i] = t / b;
		t %= b;
	}
}

同样的,高精度除法也需要去除前导0:

	while (lc > 1 && c[lc - 1] == 0) --lc;

完整代码如下:

#include <iostream>
#include <string>
using namespace std;

const int N = 1e6 + 10;

int a[N], b, c[N];

int la, lc;

typedef long long LL;

void sub(int* a, int b, int* c)
{
	LL t = 0;
	for (int i = lc - 1; i >= 0; --i)
	{
		t = t * 10 + a[i];
		c[i] = t / b;
		t %= b;
	}

	while (lc > 1 && c[lc - 1] == 0) --lc;
}

int main()
{
	string x;
	cin >> x >> b;

	la = x.size(), lc = la;
	for (int i = 0; i < la; ++i) a[i] = x[la - 1 - i] - '0';

	sub(a, b, c);

	for (int i = lc - 1; i >= 0; --i) cout << c[i];
	cout << endl;

	return 0;
}

测试:

6 总结

高精度算法的应用场景广泛,如加密货币交易和挖矿过程中用于处理大数运算,确保交易的安全性和准确性;在物理和化学模拟中提供更精确的计算结果等。

在处理高精度计算时,需要处理好以下几个问题:

  1. 数据的接收方法和存储方法。
  2. 进位、借位的处理。

总之,高精度算法是解决C++中超出常规数据类型表示范围的数值运算的重要手段,通过合理的设计和实现,可以提高程序的计算能力和准确性。

对于C语言中的高精度加法和乘法,可以使用字符串或数组来实现。下面是一个简单的示例代码: ```c #include <stdio.h> #include <string.h> // 字符串形式的高精度加法 char* highPrecisionAdd(char* num1, char* num2) { int len1 = strlen(num1); int len2 = strlen(num2); int maxLen = (len1 > len2 ? len1 : len2) + 1; char* result = (char*)malloc((maxLen + 1) * sizeof(char)); // 申请足够的空间存放结果 result[maxLen] = '\0'; // 结果字符串结尾 int carry = 0; // 进位 int i = len1 - 1; int j = len2 - 1; int k = maxLen - 1; while (i >= 0 || j >= 0) { int n1 = (i >= 0 ? num1[i] - '0' : 0); int n2 = (j >= 0 ? num2[j] - '0' : 0); int sum = n1 + n2 + carry; result[k] = sum % 10 + '0'; // 计算当前位的值 carry = sum / 10; // 计算进位 i--; j--; k--; } if (carry > 0) { result[k] = carry + '0'; // 如果最高位有进位,需要在最前面加上 } else { // 去掉最高位的0 char* tmp = (char*)malloc(len1 + 1); strcpy(tmp, result + 1); free(result); result = tmp; } return result; } // 数组形式的高精度乘法 void highPrecisionMultiply(int num1[], int len1, int num2[], int len2, int result[]) { int maxLen = len1 + len2; for (int i = 0; i < maxLen; i++) { result[i] = 0; // 初始化结果数组为0 } for (int i = len1 - 1; i >= 0; i--) { for (int j = len2 - 1; j >= 0; j--) { int mul = num1[i] * num2[j]; int sum = result[i + j + 1] + mul; result[i + j] += sum / 10; result[i + j + 1] = sum % 10; } } } int main() { // 高精度加法示例 char num1[] = "123456789"; char num2[] = "987654321"; char* sum = highPrecisionAdd(num1, num2); printf("Sum: %s\n", sum); free(sum); // 高精度乘法示例 int num3[] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; int num4[] = {9, 8, 7, 6, 5, 4, 3, 2, 1}; int len1 = sizeof(num3) / sizeof(num3[0]); int len2 = sizeof(num4) / sizeof(num4[0]); int result[len1 + len2]; highPrecisionMultiply(num3, len1, num4, len2, result); printf("Product: "); for (int i = 0; i < len1 + len2; i++) { printf("%d", result[i]); } printf("\n"); return 0; } ``` 以上代码示例了如何使用字符串和数组来实现高精度加法和乘法。其中,高精度加法使用字符串存储数字,逐位相加,并处理进位。高精度乘法使用数组存储数字,按照乘法规则逐位计算,并将结果保存在结果数组中。运行以上示例代码,可以得到高精度加法和乘法的结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值