1.什么是高精度算法
根据我的理解,就是利用数组来存储数字并模拟实现计算过程。
那为什么要进行这样麻烦的操作呢?
众所周知,C语言提供的各种变量类型都是有上下限的,那么当我们要计算的数字超过了这个上下限的时候就无法存储,也更无法进行计算。于是我们就想到用数组来存储数据,将数组的每一个元素作为数字的一位,这样就可以将大的离谱的数据进行存储。
当然存在数组中的数据自然无法利用算术操作符来进行运算,于是就要求我们编程来模拟实现算术过程。
2.整体设计
先声明一下,由于数组是将每一位单独存放的,所以数据无法带上负号,以下代码都是建立在输入的数据为正数的前提下。除了乘法以外,加减除,都是我的原创写法,如果有什么不对的地方还望指出。
2.1.数据在数组中倒放
那么,为什么要将数据倒放在数组中呢?如果数据是正放在数组中的,那么当计算过程中发生数据发生进位时,整个数组的元素都需要向后移动一个下标,十分的麻烦;而数据到放在数组中时,进位的时候只需要把进位的值放到下标加一的元素那里就行了。
2.2.读入两个数据
我们需要两个数组来存储读入的数据,两个数据之间不能直接用算术操作符来进行运算,但是他们的每一位的数据应当能够相互进行算术运算,所以在运算的过程中两个大数字一定是存储在int类型数组中的。
我们已经分析过,元素需要倒放在int类型数组中,但用户肯定不会倒着输入数据,那我们就需要另外两个数组来接收用户输入的数据。而这两个数组并不适合定义为int类型,如果定义为int类型,那么我们就只能这么来接收数据
int i = 0, j = 0;
while(scanf("%1d", &num1[i]) != EOF)i++;
while(scanf("%1d", &num2[j]) != EOF)j++;
但如果这两个数组是char类型的话,我们就可以这么来接收
scanf("%s", num1);
getchar();//清除空格
scanf("%s", num2);
无论是时间复杂度还是代码简易程度都是第二种更优
在把数据读入之后,再这样把数据倒序存放到int类型数组中
int la, lb, i, j;
la = strlen(num1);//计算两个数字的长度
lb = strlen(num2);
int a[101] = { 0 };
int b[101] = { 0 };
//将两个字符数组倒顺存在两个int数组中
for (i = 0; i < la; i++)
{
a[i] = num1[la - 1 - i] - '0';
}
for (j = 0; j < lb; j++)
{
b[j] = num2[lb - 1 - j] - '0';
}
2.3.结果的存放
与读取相反,存放时肯定是先把计算的结果同样倒序地存在一个int类型数组中,最后需要打印时,再以正序存放到char数组中,以字符串形式进行打印,这里我将结果存放在c数组中,以下的代码目的是将数据放到num3这个char数组中。这里num3也可以直接定义为普通的数组,只要留好足够的空间就行,我懒得想该留多少,就用动态数组把他的空间留成刚好可以放下结果和一个'\0'。
while (c[lc] == 0 && lc > 0)
lc--;//消除高位的零
char* num3 = (char*)malloc(sizeof(char) * (lc + 2));
for (i = 0; i <= lc; i++)
{
num3[i] = c[lc - i] + '0';
}
num3[lc + 1] = 0;//加上\0防止printf打印乱码
printf("%s\n", num3);
消除高位零就是避免出现结果为001234的情况。
高位零为什么会出现?
在定义c数组的时候lc = la + lb,而为了避免越界,我给c数组开辟了lc + 2的空间,并将所有元素初始化为零。即使是乘法,结果最高位的下标也不会超过lc,其他运算则更不会用得完这些空间,那么就会存在着高位零。
2.4.main函数整体代码
int main()
{
char choice = 0;
char num1[101];//定义两个字符数组来输入数字
char num2[101];
again:
printf("请输入表达式,数字与符号之间用空格隔开:\n");
scanf("%s", num1); getchar();
scanf("%c", &choice); getchar();
scanf("%s", num2);
if (choice != '+' && choice != '-' && choice != '*' && choice != '/')
{
printf("输入错误,请重新输入!\n");
goto again;
}
int la, lb, lc, i, j;
la = strlen(num1);//计算两个数字的长度
lb = strlen(num2);
lc = la + lb;
int a[101] = { 0 };
int b[101] = { 0 };
int* c = (int*)malloc(sizeof(int) * (lc + 2));//开辟大小合适的数组c
for (i = 0; i < lc + 2; i++)//由于c需要存储结果,且之后的运算中c元素的值会影响结果,所以在这里将其全部初始化为0
{
c[i] = 0;
}
//将两个字符数组倒顺存在两个int数组中
for (i = 0; i < la; i++)
{
a[i] = num1[la - 1 - i] - '0';
}
for (j = 0; j < lb; j++)
{
b[j] = num2[lb - 1 - j] - '0';
}
switch (choice)
{
case '+':addition(a, b, c, la, lb); break;
case '-':if(subtraction(a, b, c, la, lb))printf("-"); break;
case '*':multiplication(a, b, c, la, lb); break;
case '/':division(a, b, c, la, lb); break;
}
while (c[lc] == 0 && lc > 0)
lc--;//消除高位的零
char* num3 = (char*)malloc(sizeof(char) * (lc + 2));
for (i = 0; i <= lc; i++)
{
num3[i] = c[lc - i] + '0';
}
num3[lc + 1] = 0;//加上\0防止printf打印乱码
printf("%s\n", num3);
free(c);
free(num3);
return 0;
}
通过用户两个数据间的符号来判断进行何种算术运算:加法调用addition,减法调用subtraction(正数返回0,负数返回1),乘法调用multiplication,除法调用division。
3.加法
模拟实现算术操作,那么我们到底是要模拟个什么呢?
每一个元素只存放一位的目的就在于此。众所周知,最出名的算法一般就是最好的,而我们所知道的最出名四则运算的算法就是 “竖式”(怕有人不知道什么是竖式我就多说一句,竖式就是你在做数学题时在草稿本上画的那个东西,因为我第一次听到这个词的时候也不知道是什么)。
根据竖式的原理,我们可以设计出如下的代码。
void addition(int* a, int* b, int* c, int la, int lb)//加法
{
for (int i = 0; i < la || i < lb; i++)
{
c[i] += (a[i] + b[i]);//对位相加放到结果中
c[i + 1] += (c[i] / 10);//进位
c[i] %= 10;//进位结束后留在该位的数
}
}
4.乘法
为什么不先说减法呢?尝试过实现减法除法的都知道这两个的难度和加法乘法不是一个量级的,所以我们先把简单的解决一下。
void multiplication(int* a, int* b, int* c, int la, int lb)//乘法
{
for (int i = 0; i < la; i++)//两数相乘并进位
{
for (int j = 0; j < lb; j++)
{
c[i + j] += (a[i] * b[j]);
c[i + j + 1] += (c[i + j] / 10);
c[i + j] %= 10;
}
}
}
同样的简单,同样的简短。 (虽然也需要动脑筋,但和减法除法比起来算是很简单了)
同样列出竖式就能很好理解的代码,再看别人的文章的时候他们都有画图举例子,大家可以去参考一下。由于我没有平板,不好画图就不举例了,毕竟只要知道这是用竖式的原理写出来的就很好理解了,你可以自己举点例子用竖式算算,回忆一下这个过程。
5.减法
5.1.subtraction函数,减法实现的主体逻辑
减法一定要先于除法完成,回顾一下竖式中除法的算法就知道,期间是会涉及到减法的。
int subtraction(int* a, int* b, int* c, int la, int lb)//减法 c = a - b//得到绝对值,结果为正则返回0,结果为负则返回1
{
int i = 0;
if (la > lb)
{
aBig:
for (i = 0; i < la; i++)
{
if (a[i] < b[i])//不够减
{
subtract(a, b, i);
c[i] = a[i] - b[i] + 10;
}
else
{
c[i] = a[i] - b[i];
}
}
return 0;
}
else if (la < lb)
{
bBig:
for (i = 0; i < lb; i++)
{
if (a[i] > b[i])//不够减
{
subtract(b, a, i);
c[i] = b[i] - a[i] + 10;
}
else
{
c[i] = b[i] - a[i];
}
}
return 1;
}
else
{
if (compare(a, b, la - 1))
goto aBig;
else
goto bBig;
}
}
我们将数据存储在数组中,存储的是数据的绝对值,那么在开减之前应该要先比较一下两个数组中数据的大小。
先比较长度,比较长的自然是较大的(也就是比较la,lb的大小)。
如果两个数的长度相等,我们就利用compare这个函数来比较(5.2)。
比较完之后就开始进行减法操作,原理依旧是竖式。
而我们在利用竖式完成减法时,可能会出现被减数某一位不够减的情况。这时候我们就利用subtract函数(5.3)来实现向高位借数的操作,并在这一位的结果中加上10。
5.2.compare函数,比较两个相同长度数据大小
int compare(int* a, int* b, int k)//比较a,b代表值的大小
{
if (k < 0)
return 1;
if (a[k] > b[k])
return 1;
else if (a[k] < b[k])
return 0;
else
return compare(a, b, k - 1);
}
利用递归实现,k为a,b两个数据的长度。比较从最高位比起,如果a大返回1,b大返回0,如果该位相同就继续比较下一位。
5.3.subtract函数,实现向高位借数
void subtract(int* a, int* b, int k)//不够减,向高位借,只实现借
{
if (a[k + 1] > 0)
{
a[k + 1] -= 1;
return;
}
else
{
subtract(a, b, k + 1);
}
a[k] = 9;
}
同样是递归实现,这也是把这两部分单独做成函数的原因。
k代表需要向高位借数的位数,如果较高位够借,那么较高位减一并返回。
如果不够借(该位为零),那么就向下一位借。
借成功后,之前不够借的位的值修改为9。
6.除法
6.1.division函数,除法的主体逻辑(其实也不算,divide才是大头)
void division(int* a, int* b, int* c, int la, int lb)//除法(保留整数)
{
if (la > lb)
{
aBig:
divide(a, b, c, la, lb);
return;
}
else if (la < lb)
{
bBig:
return;
}
else
{
if (compare(a, b, la - 1))
goto aBig;
else
goto bBig;
}
}
同样需要对a,b的大小进行比较,如果a小于b的话,那么结果自然就是0。
如果a比较大,那么我们就把剩下的工作交给divide函数(6.2)来做吧。
6.2.divide函数,真正实现除法的函数
void divide(int* a, int* b, int* c, int la, int lb)
{
int k, n = lb;
op:
k = la - lb;
int flag = 1;
if (k < 0)
return;
while (compare(a + k, b, lb))
{
flag = 0;
subtraction_x(a + k, b, lb);
c[k]++;
if (a[la - 1] == 0)break;
}
if (flag)//解决例如25000/50的情况
{
lb++;
b[lb] = 0;
goto op;
}
while (a[la - 1] == 0)la--;
return divide(a, b, c, la, n);
}
看上去不长,但真的花了我不少时间。
虽然但是,我想了想这里不画图的话肯定有很多人看不懂,所以就大致画了下。
每个部分的相除是通过减法来完成,a+k每减一个b,c[k]就加加。我稍微改了下subtraction函数,写了个阉割版subtraction_x函数(6.3)来完成a+k与b的相减。
之前减法的subtraction函数是将结果存在c中,这里可以用a代替c,la代替lc传到subtraction函数中也可以达到相同的效果。但是我在写的过程中搞忘了,只不过阉割版的效率确实会提高一些。
如果在第一次运算的时候,a+k比b小,那么就会在没有对任何变量进行操作的情况下再次调用divide函数,于是就加入了flag这个变量来解决这个问题。如果while循环一次都没有进行过,那么就会给b添加一个高位零,使其从a的较低一位来开始比较。
6.3.subtraction_x函数,阉割版subtraction函数
void subtraction_x(int* a, int* b, int k)//a -= b
{
for (int i = 0; i < k; i++)
{
if (a[i] < b[i])//不够减
{
subtract(a, b, i);
a[i] = a[i] - b[i] + 10;
}
else
{
a[i] = a[i] - b[i];
}
}
}
7.全部代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void subtract(int* a, int* b, int k)//不够减,向高位借,只实现借
{
if (a[k + 1] > 0)
{
a[k + 1] -= 1;
return;
}
else
{
subtract(a, b, k + 1);
}
a[k] = 9;
}
void subtraction_x(int* a, int* b, int k)//a -= b
{
for (int i = 0; i < k; i++)
{
if (a[i] < b[i])//不够减
{
subtract(a, b, i);
a[i] = a[i] - b[i] + 10;
}
else
{
a[i] = a[i] - b[i];
}
}
}
void divide(int* a, int* b, int* c, int la, int lb)
{
int k, n = lb;
op:
k = la - lb;
int flag = 1;
if (k < 0)
return;
while (compare(a + k, b, lb))
{
flag = 0;
subtraction_x(a + k, b, lb);
c[k]++;
if (a[la - 1] == 0)break;
}
if (flag)//解决例如25000/50的情况
{
lb++;
b[lb] = 0;
goto op;
}
while (a[la - 1] == 0)la--;
return divide(a, b, c, la, n);
}
int compare(int* a, int* b, int k)//比较a,b代表值的大小
{
if (k < 0)
return 1;
if (a[k] > b[k])
return 1;
else if (a[k] < b[k])
return 0;
else
return compare(a, b, k - 1);
}
void division(int* a, int* b, int* c, int la, int lb)//除法(保留整数)
{
if (la > lb)
{
aBig:
divide(a, b, c, la, lb);
return;
}
else if (la < lb)
{
bBig:
return;
}
else
{
if (compare(a, b, la - 1))
goto aBig;
else
goto bBig;
}
}
int subtraction(int* a, int* b, int* c, int la, int lb)//减法 c = a - b//得到绝对值,结果为正则返回0,结果为负则返回1
{
int i = 0;
if (la > lb)
{
aBig:
for (i = 0; i < la; i++)
{
if (a[i] < b[i])//不够减
{
subtract(a, b, i);
c[i] = a[i] - b[i] + 10;
}
else
{
c[i] = a[i] - b[i];
}
}
return 0;
}
else if (la < lb)
{
bBig:
for (i = 0; i < lb; i++)
{
if (a[i] > b[i])//不够减
{
subtract(b, a, i);
c[i] = b[i] - a[i] + 10;
}
else
{
c[i] = b[i] - a[i];
}
}
return 1;
}
else
{
if (compare(a, b, la - 1))
goto aBig;
else
goto bBig;
}
}
void addition(int* a, int* b, int* c, int la, int lb)//加法
{
for (int i = 0; i < la || i < lb; i++)
{
c[i] += (a[i] + b[i]);
c[i + 1] += (c[i] / 10);
c[i] %= 10;
}
}
void multiplication(int* a, int* b, int* c, int la, int lb)//乘法
{
for (int i = 0; i < la; i++)//两数相乘并进位
{
for (int j = 0; j < lb; j++)
{
c[i + j] += (a[i] * b[j]);
c[i + j + 1] += (c[i + j] / 10);
c[i + j] %= 10;
}
}
}
int main()
{
char choice = 0;
char num1[101];//定义两个字符数组来输入数字
char num2[101];
again:
printf("请输入表达式,数字与符号之间用空格隔开:\n");
scanf("%s", num1); getchar();
scanf("%c", &choice); getchar();
scanf("%s", num2);
if (choice != '+' && choice != '-' && choice != '*' && choice != '/')
{
printf("输入错误,请重新输入!\n");
goto again;
}
int la, lb, lc, i, j;
la = strlen(num1);//计算两个数字的长度
lb = strlen(num2);
lc = la + lb;
int a[101] = { 0 };
int b[101] = { 0 };
int* c = (int*)malloc(sizeof(int) * (lc + 2));//开辟大小合适的数组c
for (i = 0; i < lc + 2; i++)//由于c需要存储结果,且之后的运算中c元素的值会影响结果,所以在这里将其全部初始化为0
{
c[i] = 0;
}
//将两个字符数组倒顺存在两个int数组中
for (i = 0; i < la; i++)
{
a[i] = num1[la - 1 - i] - '0';
}
for (j = 0; j < lb; j++)
{
b[j] = num2[lb - 1 - j] - '0';
}
switch (choice)
{
case '+':addition(a, b, c, la, lb); break;
case '-':if(subtraction(a, b, c, la, lb))printf("-"); break;
case '*':multiplication(a, b, c, la, lb); break;
case '/':division(a, b, c, la, lb); break;
}
while (c[lc] == 0 && lc > 0)
lc--;//消除高位的零
char* num3 = (char*)malloc(sizeof(char) * (lc + 2));
for (i = 0; i <= lc; i++)
{
num3[i] = c[lc - i] + '0';
}
num3[lc + 1] = 0;//加上\0防止printf打印乱码
printf("%s\n", num3);
free(c);
free(num3);
return 0;
}
8.客套话
觉得写的好的话就点点赞吧!祝大家新年快乐!