大数运算
最近思考了关于大数运算的问题。我们知道在微型计算机中要想实现几十位甚至是上千位的四则运算是不可能的。我在网上找一些关于此类计算的问题,大概有两种解决方法,一种是用二进制和移位,这种方法非高手不能解之,另一种方法是用数组模拟人工手算。我花了4天左右时间把这个大数的四则运算程序写完了,其中加法,减法和乘法相对简单实现,唯独除法运算繁琐一些。我依次说明这四则运算的解决方法。(因为刚刚写完没有对代码进行优化,所以大家看它可能有些不适)
加法运算:大概的思路是这样的,我们用两个字符串接受输入的字符,例如6 和 6.14 (这只是随便举的例子,并非特例)相加,用s1保存"6",s2保存"3.14",输出结果保存在一个整形数组resu中,这些数组的大数都是256,当然可以随意定义。要将6和6.14相加,这个小数点是最麻烦的,我们不妨将小数点先去掉,将6和3对齐,然后将从6和3的相加结果放入resu中,1和6也放入resu中,接着对resu中的数字进行进位操和,再移动小数点。
将小数点去掉:
void rid_sign(char* s, char* objStr)
{//去除符号,将字符串赋给objStr
int i = 0, j = 0;
while(s[i] == '0' && s[i + 1] != '0')
{//去效位置前面的数字
i++;
}
while(s[i] != '\0')
{//赋值
if(s[i] > 47 && s[i] < 58)
objStr[j++] = s[i];
i++;
}
while(j < MAX)
{//结束符
objStr[j++] = '\0';
}
}
要对齐整数位和小数位,我采用的方法是先获得小数点位置(每个整数后面默认有一个小数点,我用了#define POINT -1000 来表示小数点),然后两个小数点位置相减
较大数从resu的2号位置开始存放(我用数组0号位置作为符号位,1表示正,-1表标负,1号位置先空着,如果有进位可能要用到),较小的数从2+小数点位置相减之差,这样就完成了对齐操作:
int get_pointAddr(char* s)
{//获取小数点位置
int i = 0, j = 0;
while((s[i] == '0') && (s[i + 1] != '.'))
{//去掉有效位置前面的数字
i++;
}
while(s[i] != '\0')
{
if(s[i] == '.')
return j;
j++;
i++;
}
return j;
}
接着是进位和设置小数点:进位的方法很简单,相加之后从resu的2号位置开始的情况是这样的:
只要从后面开始依次进位就行了。顺便把退位的函数也放一起:
void numUp(int* arr, int n)
{//进位
for(int i = n - 1; i > -1; i--)
{//从末尾开始依次作进位
if(*(arr + i) > 9)
{
*(arr + i - 1) += *(arr + i) / 10;
*(arr + i) = *(arr + i) % 10;
}
}
}
void numDown(int* arr, int n)
{//退位
for(int i = n - 1; i > -1; i--)
{//从末尾开始依次退位
if(*(arr + i) < 0)
{
*(arr + i - 1) -= 1;
*(arr + i) += 10;
}
}
}
void move_point(int* arr, int n, int bit)
{//从左到右移动小数点
int i = n -1, j = 0;
for(i = n - 1, j = 0; j < n - bit - 3; i--, j++)
{//n为数组前n位,bit为移动的位数
arr[i] = arr[i - 1];
}
arr[i] = POINT;
}
int* add(char* s1, char* s2)
{
if(!(int)(*s1 - '-') * (int)(*s2 - '-'))
sub(s1, s2);//如果只有一个为负号则作乘法运算
else
{
int i = 0, j = 0, ptAddr1 = 0, ptAddr2 = 0,//小数点位置
len1 = 0, len2 = 0, dis = 0;//两个字符串的长度, 小数点位置之差
char temp1[MAX], temp2[MAX];
rid_sign(s1, temp1);//去除符号将字符串赋给temp1
rid_sign(s2, temp2);
ptAddr1 = get_pointAddr(s1);//获得小数点位置
ptAddr2 = get_pointAddr(s2);
len1 = strlen(temp1);//计算无符号字符串的长度
len2 = strlen(temp2);
int* resu = new int[MAX];//符号,进位,小数点
for(int c = 0; c < MAX; c++)
resu[c] = 0; //初始化整形数组
dis = ptAddr1 - ptAddr2;//小数点位置之差
for(i = 2 + (dis < 0 ? abs(dis) : 0),//为了对齐小数点位置
j = 0; j < len1; i++)
*(resu + i) += (int)(*(temp1 + j++) - '0');//s1与resu相加
for(i = 2 + (dis > 0 ? dis : 0), j = 0; j < len2; i++)
*(resu + i) += (int)(*(temp2 + j++) - '0');//s2与resu相加
numUp(resu, len1 + len2 + 2);//进位
//移动小数点
move_point(resu, len1 + len2 + 3, ptAddr1 > ptAddr2 ? ptAddr1 : ptAddr2);
return resu;//返回整形数组
}
return NULL;
}
int compare(char* s1, char* s2, const int beg, const int end)
{//比较s1, s2的大小, 若s1 > s2则返回true
int i = beg, j = beg, dis = 0;
int ptAddr1 = 0, ptAddr2 = 0;
ptAddr1 = get_pointAddr(s1);
ptAddr2 = get_pointAddr(s2);
if(ptAddr1 < ptAddr2)
return -1;
else if(ptAddr1 > ptAddr2)
return 1;
while(s1[i] < 49 || s1[i] > 58)
{//去除无效位
i++;
}
while(s2[j] < 49 || s2[j] > 58)
{
j++;
}
dis = ptAddr1 - ptAddr2;
while(i < end + 1)
{//有效位相等情况下依次比较每个数的大小
if(*(s1 + i) > *(s2 + j) && dis == 0)
return 1;
else if(*(s1 + i) < *(s2 + j))
return -1;
i++;
j++;
}
return 0;
}
int* sub(char* s1, char* s2)//s2为被减数
{//减法
char* max = NULL, * min = NULL,//分别指向无符号较大数和较小数
flag = 0;//正负数标志
int r = compare(s1, s2, 0, strlen(s1));
if(r == 1){//max指向无符号较大数, min指向无符号较小数
max = s1;
min = s2;
flag = 1;
}
else if(r == -1){//s1 < s2
max = s2;
min = s1;
flag = -1;
}
else{//s1 == s2
max = s1;
min = s2;
flag = 0;
}
int i = 0, j = 0,
ptAddr1 = 0, ptAddr2 = 0,//小数点位置
len1 = 0, len2 = 0, dis = 0;//两个字符串的长度, 小数点位置之差
char temp1[MAX], temp2[MAX];
rid_sign(max, temp1);//去除符号将较大字符串赋给temp1
rid_sign(min, temp2);
ptAddr1 = get_pointAddr(max);//获得小数点位置
ptAddr2 = get_pointAddr(min);
len1 = strlen(temp1);//计算无符号字符串的长度
len2 = strlen(temp2);
int* resu = new int[MAX];//符号,进位,小数点
for(int c = 0; c < MAX; c++)
resu[c] = 0; //初始化整形数组
dis = ptAddr1 - ptAddr2;//小数点位置之差
for(i = 2 + (dis < 0 ? abs(dis) : 0),//为了对齐小数点
j = 0; j < len1; i++)
*(resu + i) += (int)(*(temp1 + j++) - '0');//s1与resu相加
for(i = 2 + (dis > 0 ? dis : 0), j = 0; j < len2; i++)
*(resu + i) -= (int)(*(temp2 + j++) - '0');//s2与resu相加
numDown(resu, len1 + len2 + 2);//退位
//移动小数点
move_point(resu, len1 + len2 + 3, ptAddr1 > ptAddr2 ? ptAddr1 : ptAddr2);
*resu = flag;//正负号标志
return resu;//返回整形数组
}
int* multi(char* s1, char* s2)
{
int i = 0, j = 0, len1 = 0, len2 = 0;//temp1, temp2的长度
int ptAddr1 = 0, ptAddr2 = 0;//小数点位置
char temp1[MAX], temp2[MAX];//存放无符号的s1, s2
rid_sign(s1, temp1);//去除所有符号,将值赋给temp1
rid_sign(s2, temp2);
len1 = strlen(temp1);//得到temp1字符串的长度
len2 = strlen(temp2);
ptAddr1 = get_pointAddr(s1);//获得s1字符串的小数点位置
ptAddr2 = get_pointAddr(s2);
int* resu = new int[MAX];//比两个数和多一个小数点和符号位
for(int c = 0; c < MAX; c++)
resu[c] = 0; //初始化整形数组
for(i = len1 - 1; i > -1 ; i--)
for(j = len2 - 1; j > -1; j--)//相乘
resu[i + j + 3] += (int)((temp2[j] - '0') * (temp1[i] - '0'));
numUp(resu, len1+len2 + 2);//判断进位
move_point(resu, len1 + len2 + 3, ptAddr1 + ptAddr2);//设定小数点
if(((int)(s1[0] - '-') * (int)(s2[0] - '-')))//第一个元素相乘
resu[0] = 1;//设定数组第一个元素,如果为正则为0
else
resu[0] = -1;
return resu;
}
除法运算:我先写了几个要用于除法运算的函数,每个函数第一个“{”后我都写了说明:
void itos(int* arr, char* s, int beg)
{//整形数组到字符串
int i = 0, j = 0, k = 0;
while(arr[j++] != POINT)
;//j一直到小数点标志
for(i = beg; i < j - 1; i++)
{//从beg开始一直到小数点位置之前
s[k++] = arr[i] + '0';
}
s[k] = '\0';
}
void insert(char* s, char c = '0')
{//向字符串末尾插入一个字符串,缺省值为'0'
int i = 0;
while(s[i] != '\0')
{//一直查找到字符串末尾
i++;
}
s[i] = c;
s[i + 1] = '\0';
}
bool isZero(int *arr, int n)
{//判断是否为0
int i = 1;
while(*(arr + i) != POINT)
{//如果从位置1开始小数点标志前都为0则为返回假
if(*(arr + i) > 0)
return false;
i++;
}
return true;
}
bool isSet0(int* arr, int i)
{//判断在第i个位置是否有小数点
if(*(arr + i) > -1 && *(arr + i) < 10)
return false;//如果该位置存在一个小于10的正数则返回假
else if(*(arr + i) == 0)//该位置上的数为0
return true;
}
void rid_sign(char* s1, char* s2, char* temp1, char* temp2)
{//重载,用于除法计算
int i = 0, j = 0, k = 0;
int smallNum1 = 0, smallNum2 = 0;//小数位数
int len1 = 0, len2 = 0;
int ptAddr1 = 0, ptAddr2 = 0;//小数点位置
rid_sign(s1, temp1);//temp1为无符号的s1
rid_sign(s2, temp2);
ptAddr1 = get_pointAddr(s1);//行到小数点位置
ptAddr2 = get_pointAddr(s2);
len1 = strlen(temp1);
len2 = strlen(temp2);
i = ptAddr1; j = ptAddr2;
while(s1[i++] != '\0')
{//小数点之前如果没有结束符则小数加1
smallNum1++;
}
while(s2[j++] != '\0')
{
smallNum2++;
}
for(k = strlen(temp1); k < len1 + smallNum2 - 1; k++)
{//补0
temp1[k] = '0';
}
temp1[k] = '\0';
for(k = strlen(temp2); k < len2 + smallNum1 - 1; k++)
{
temp2[k] = '0';
}
temp2[k] = '\0';
}
其中最后一个rid_sign函数重载了之前的函数,在这里只用于除法的去符号操作,因为在去除小数点后要进行补0操作。
我举一个例子说明除法运算的程序执行过程:6 和 3.14,
a. 去0后的结果为600和314,即是600(temp1) 除314 ( temp2)
b. 600 - 314 = 286 (tempDiv), 执行正数操作,resu[i]++, 继续进行减法操作
c. 因为 286 - 314 < 0 , 执行负数操作, 若下一次减法运算小于0,则该位置商0, 若下一位置没有除数或为0,则插入0,否则插入除数下一位置的数resu的索引 i++,即商到了下一位。
d. 若相数结果相减 = 0, 仍要进行一次判断,若temp1不为空则将其加入到 tempDiv 中。
e. 循环判断,若达到最大精度或是相除已经有结果,则退出循环。
int* divi(char* s1, char * s2)
{//除法
/*定义变量*/
char temp1[MAX], temp2[MAX];//无符号的s1,s2
char tempDiv[MAX] = {""};//用于保存前n位除数
int* resu = new int[MAX];//商的结果
int* nArr = new int[MAX];//减法所得结果
int i = 0, j = 0, m = 0, n = 0, len1 = 0, len2 = 0;//无符号S1,S2的长度
bool isSetPoint = false;//是否设置小数点
/*初始化*/
for(i = 0; i < MAX; i++)
{//初始化整形结果数组resu,字符串subStr
resu[i] = 0;
nArr[i] = 0;
}
i = 0;
rid_sign(s1, s2, temp1, temp2);
len1 = strlen(temp1);//计算temp1的长度
len2 = strlen(temp2);
strncpy(tempDiv, temp1, len2);//复制前len2位除数到subStr
while((j++ == -1) || (!isZero(nArr, MAX) && i < MAX))
{
nArr = sub(tempDiv, temp2);//除数前n位减被除数
if(*nArr == -1)
{//负数
if(isSet0(resu, i + 1)) //若下一次减法运算小于0,则该位置商0
resu[i + 1] = 0;
if(temp1[len2 + i] < '1')
{//若下一位置没有除数或为0,则插入0,否则插入除数下一位置的数
insert(tempDiv, '0');
if(!isSetPoint && temp1[len2 + i] == '\0'){
resu[i++ + 2] = POINT;
isSetPoint = true;
}
}
else
insert(tempDiv, temp1[len2 + i]);
i++;
}
else if(*nArr == 1)
{//正数
m = 0;
while(*(nArr + m) != POINT)
m++;//判断是否置小数点标志
if(i > 0)
{
for(n = 0; n < i; n++)
tempDiv[n] = '0';
for(; n < m; n++)
tempDiv[n] = '\0';
}
resu[i + 1] += 1;
itos(nArr, tempDiv, 2);//前n位除数减被除数的值
}
else
{//相等
m = 0;
while(*(nArr + m) != POINT)
m++;
if(i > 0)
{//清空tempDiv的值
for(n = 0; n < i; n++)
tempDiv[n] = '0';
for(; n < m; n++)
tempDiv[n] = '\0';
}
resu[i + 1] += 1;
itos(nArr, tempDiv, 2);
if(temp1[len2 + i] != '\0')
{//判断除数下一位置是否有数值
if(isSet0(resu, i + 1)) //若下一次减法运算小于0,则该位置商0
resu[i++ + 1] = 0;
insert(tempDiv, temp1[len2 + i]);
}
else
{//若没有数值则退出
if(!isSetPoint && temp1[len2 + i] == '\0'){
resu[i + 2] = POINT;
isSetPoint = true;
}
break;
}
i++;
}
}
if(!((int)(s1[0] - '-') * (int)(s2[0] - '-')))//判断正负号
resu[0] = -1;
else
resu[0] = 1;
return resu;//返回结果数组
}