高精度算法(High Accuracy Algorithm)是处理大数字的数学计算方法。在一般的科学计算中,会经常算到小数点后几百位或者更多,当然也可能是几千亿几百亿的大数字。一般这类数字我们统称为高精度数。(摘自百度百科)
在C/C++中,我们经常会碰到限定数据范围的情况。
C++标准规定:
int占一个机器字长。在32位系统中int占32位,也就是4个字节,所以int的范围是[-2^31,2^31-1],
为10^9数量级;
long long的范围是[-2^63,2^63-1],为10^18数量级。那么如果我们需要计算10^100该怎么办?
a2 | a1 |
b2 | b1 |
c2 | c1 |
c1 = a1 + b1,c2 = a2 + b2.但是我们还有一个进位的问题。
这里我们以 68 + 25 = 93 为例。
a1 = 8,b1 = 5,c1 = 13;a2 = 6,b2 = 2,c2 = 8。此时我们利用一个累加的办法:
c[ i ] += a[ i ] + b[ i ];即可在每一次进行加法的时候将低位的进位加上来。
crr[i] += arr[i] + brr[i]; // 累加是因为后一步才计算的进位
crr[i + 1] = crr[i] / 10;
crr[i] = crr[i] % 10;
或许已经有小伙伴发现,我们的字符串下标是0~n,为什么我的是n~0?
因为0~n不符合我们平时计算的习惯,所以我们将第一个数和最后一个数调换,第二数和倒数第二个数调换,以此类推。
到此,我们还有最后一个问题,就是处理多余的0。我们一开始给数组全部赋值为0,假设我们设置数组长度为3,那么我们68 + 25 计算出来的结果就是093,所以我们需要删除前面的0。
if (crr[len_c] == 0 && len_c > 0) // 删除前面的0
{
len_c--;
}
那么为什么lc要大于0呢,假设我们仅仅计算0+0,最后的结果就是0,这里不能删除!
#include
#include
#include
#include
using namespace std;
char str1[505], str2[505];
int arr[505], brr[505], crr[505];
int main()
{
int len_a, len_b, len_c;
scanf("%s", str1);
scanf("%s", str2);
len_a = strlen(str1);
len_b = strlen(str2);
for (int i = 0; i < len_a; i++)
{
arr[len_a - i] = str1[i] - '0'; // 将字符转化成数字(ASCII值计算),并且将字符逆序计算
}
for (int i = 0; i < len_b; i++)
{
brr[len_b - i] = str2[i] - '0';
}
len_c = max(len_a, len_b) + 1;
for (int i = 1; i < len_c; i++)
{
crr[i] += arr[i] + brr[i]; // 累加是因为后一步才计算的进位
crr[i + 1] = crr[i] / 10;
crr[i] = crr[i] % 10;
}
if (crr[len_c] == 0 && len_c > 0) // 删除前面的0
{
len_c--;
}
for (int i = len_c; i > 0; i--)
{
printf("%d",crr[i]);
}
return 0;
}
高精度减法与高精度加法在基本操作上都相同,我们同样利用字符数组存储数据,然后模拟减法运算法则。
我们现在求解A - B,假设A = 2383,B = 8371.
因为用一个大的数减去一个小的数,比较容易计算,只要在结果上加一个负号即可。
A - B = -(B - A)= - (5988)
if(arr[i] < brr[i]){
arr[i + 1]--;//向高位借一
arr[i]+=10;//高位借一当做十使用
}
crr[i] = arr[i] - brr[i];
#include
#include
#include
#include
using namespace std;
char str1[10086], str2[10086], str3[10086];
int arr[10086], brr[10086], crr[10086];
int flag = 0;
bool compare(char str1[], char str2[]) // 比较两个数的大小
{
int u = strlen(str1), v = strlen(str2);
if (u != v)
return u > v;
for(int i = 0;i < u;i++){
if(str1[i] != str2[i])
return str1[i] > str2[i];
}
return true;
}
int main(){
int len_a,len_b,len_c;
scanf("%s",str1);
scanf("%s",str2);
if(!compare(str1,str2)){ //s1 < s2 ,交换 s1 和 s2 的值
flag = 1;
strcpy(str3,str1);
strcpy(str1,str2);
strcpy(str2,str3);
}
len_a = strlen(str1);
len_b = strlen(str2);
for(int i = 0;i < len_a;i ++){
arr[len_a - i] = str1[i] - '0';
}
for(int i = 0;i < len_b;i ++){
brr[len_b - i] = str2[i] - '0';
}
len_c = max(len_a,len_b);
for(int i = 1;i <= len_c;i++){
if(arr[i] < brr[i]){
arr[i + 1]--;
arr[i]+=10;
}
crr[i] = arr[i] - brr[i];
}
while(crr[len_c] == 0 && len_c > 1)
len_c--;
if(flag == 1)
printf("-");
for(int i = len_c;i > 0;i --){
printf("%d",crr[i]);
}
return 0;
}
高精度乘法和高精度加法异曲同工。我们对乘法的运算法则进行模拟。与加法不同的事,我们需要用到双重循环。
for (int i = 1; i <= len_a; i++)
{
for (int j = 1; j <= len_b; j++)
{
crr[i + j - 1] += arr[i] * brr[j]; // ①
crr[i + j] += crr[i + j - 1] / 10; // ②
crr[i + j - 1] %= 10;
}
}
① 对每项进行累加
② 如果累加的值超过了10,则向前进位
同时,与加减法不同的是,如果我们计算1000 * 0,结果会出现多个0,而加减法中最多有一个前导0,所以我们要利用循环,多次删除前导0。同样,如果有且只有一个0,则保留。
for (int i = 1; i <= len; i++)
{
if (crr[len_c] == 0 && len_c > 0)
{
len_c--;
if (len_c == 1)
break;
}
}
#include
#include
#include
#include
using namespace std;
char str1[10001], str2[10001];
int arr[10001], brr[10001], crr[10001];
int main()
{
int len_a, len_b, len_c;
scanf("%s", str1);
scanf("%s", str2);
len_a = strlen(str1);
len_b = strlen(str2);
for (int i = 0; i < len_a; i++)
{
arr[len_a - i] = str1[i] - '0';
}
for (int i = 0; i < len_b; i++)
{
brr[len_b - i] = str2[i] - '0';
}
len_c = len_a + len_b; // 结果的长度最多等于两个数的长度之和(比如4位数×3位数,
//结果最多也就是7位数)
for (int i = 1; i <= len_a; i++)
{
for (int j = 1; j <= len_b; j++)
{
crr[i + j - 1] += arr[i] * brr[j];
crr[i + j] += crr[i + j - 1] / 10;
crr[i + j - 1] %= 10;
}
}
int len = len_c;
for (int i = 1; i <= len; i++)
{
if (crr[len_c] == 0 && len_c > 0)
{
len_c--;
if (len_c == 1)
break;
}
}
for (int i = len_c; i > 0; i--)
{
printf("%d", crr[i]);
}
return 0;
}
注:这里我们仅仅解析高精度除以低精度。(高精度除以高精度后续会推出)
除法的运算法则就是将被除数的每一位分别除数相除,不够就取0的方法。
规律:我们将每一位相除之后的余数乘以10再加下一位数的和作为下一次运算的被除数。
for (int i = 1; i <= len_a; ++i)// i从1开始到len_a,表示商最多有len_a位
{
crr[i] = (x * 10 + arr[i]) / b;
x = (x * 10 + arr[i]) % b;
}
#include
#include
using namespace std;
char str[5005];
long long b, arr[5005], crr[5005], x, len_a, len_c;
int main()
{
cin >> str >> b;
len_a = strlen(str);
for (int i = 1; i <= len_a; i++)// 将被除数逐位放进数组
arr[i] = str[i - 1] - '0';
for (int i = 1; i <= len_a; ++i)// i从1开始到len_a,表示商最多有len_a位
{
crr[i] = (x * 10 + arr[i]) / b;
x = (x * 10 + arr[i]) % b;
}
len_c = 1;
while (crr[len_c] == 0 && len_c < len_a)// 循环删除前导0,同乘法类似
len_c++;
for (int i = len_c; i <= len_a; ++i)
cout << crr[i];
return 0;
}
高精度运算其实就是利用数组去模拟我们的运算法则,都是较为简单的算法。
注:本文参考视频高精度算法全套(加,减,乘,除,全网最详细)_哔哩哔哩_bilibili
对其进行了修饰和优化,如果有不太懂的地方,建议食用视频。