一、引子
高精度乘法相较于高精度加法和减法有更多的不同,加法和减法是一位对应一位进行操作的,而乘法是一个数的每一位对另一个数的每一位进行操作,需要的计算步骤更多。
二、核心算法
void Calculate(int num1[], int num2[], int numres[], int len1, int len2)
{
// 核心乘法算法
for (int i = 0; i < len1; i++)
{
for (int j = 0; j < len2; j++)
{
numres[i + j] += num1[i] * num2[j];
numres[i + j + 1] += numres[i + j] / 10;
numres[i + j] = numres[i + j] % 10;
}
}
}
这一段代码是高精度乘法核心算法的实现,它模拟了我们手工进行多位数乘法的过程。这里,我们有两个数n1和n2,它们分别以字符串的形式存储,并被转换成整数数组num1和num2,其中低位在前,高位在后(即字符串“123”会被存储成整数数组{3, 2, 1})。然后我们对这两个数组执行乘法。
对于乘法,我们对于num1的每一位(由外层循环 i 控制)与num2的每一位(由内层循环 j 控制)相乘。由于我们是按位计算,所以需要考虑结果的位数(res数组的下标 i + j )。这里考虑结果的位数是最低位数,由于数组的特性,两个数字的最低位在数组中是作为第零位的,而且在数组中两个数的每一位相当于之前都是减少了一位,如果是10 * 10两个两位数相乘理论上结果是三位,但是如果将两数字的位数相加作为结果的位数,那结果就是四(2 + 2)位数,这样就不对了,但是由于数组的特性,将两数字位数相加改成两数字当前索引相加就可以了,10 * 10,索引相加是(1 + 1) = 2,而二作为索引是3位数,结果是三位数,这样就对了。在这里的原理是转换的过程,两个数的位数转成索引要减一,两个数就是减二,而结果的索引转成位数又要加一,这是就相当于减一,在前面直接用位数运算的版本中,结果是四位,这里的就相当于结果的位数减一,结果的位数就变成了三,就对了。
假设两个两位数相乘,结果的位数最低可能是三位,最高是四位,这里是按最低算的。如果放到了最高位就会导致这一步的结果扩大了十倍,对比上面的解释我们发现数组是可以直接实现将每一步结果存到正确的位上的。也就是num1[i] * num2[j]的结果可以直接放到res[i + j]里的这样是正确的。
如果不使用数组而使用数字位数的话,也就是假设 i 和 j 不是索引而是数位,那就不能将(num1的 i 位的数字) * (num2的 j 位的数字)的结果放到res的(i + j)位上,而要把结果放到res的(i + j - 1)位上。
假设是10 * 10 = 100,结果是三位,
num1[0] * num2[0]是0 * 0 = 0,res[0 + 0] = 0,res[0] = 0
num1[1] * num2[0] = 0,res[1 + 0] = 0,res[1] = 0
num1[0] * num2[1] = 0,res[0 + 1] = 0,res[1] = 0
num1[1] * num[1] = 1,res[1 + 1] = 1,res[2] = 1
这是结果就是100
这个乘法的过程是:
1. res[i + j] += n1[i] * n2[j]:这里我们将num1的第i位和num2的第 j 位相乘,然后加到结果的相应位置。由于n1和n2的下标为 i 和 j ,那么对应的结果应该是res[i + j]。这里的结果为res[i + j]就是我们上面解释的体现,用数组的特性使得这样做是正确的。
2. res[i + j + 1] += res[i + j] / 10:这里我们处理进位。如果res[i + j]的结果是两位数(即大于或等于10),我们需要将十位上的数字进位到结果的下一个位置res[i + j + 1]。
3. res[i + j] = res[i + j] % 10:保证结果数组res的每一位都是个位数,即进行模10操作。进位已经在上一步处理过了,这里确保数组res中存储的是当前位的正确数字。
这个过程会一直重复,直到num1和num2中的每一位都相乘。由于进位可能影响到最终结果的位数,结果数组res的实际使用长度可能会比num1和num2的长度总和还要大。所以,我们在定义res数组的时候要确保它有足够的空间来存储可能的最大结果,即num1长度和num2长度的和。
在乘法完成后,结果数组res中存储的是乘法结果的每一位数字,但是顺序是反的,即最低位在数组的第0个位置。在最后,我们需要将结果数组转换回字符串,并且反转回正确的顺序来输出最终的乘积。
三、处理正负
我们同时还要考虑结果的正负,对结果的正负进行判断,影响结果正负的因素是乘数的正负,这里就是同正异负,可以判断好结果的正负,然后将负号在最后打印。
在输入乘数时会有负号,所以要判断乘数是否为负数,然后还要将乘数前的负号去除,防止对之后的计算产生影响。
同时,在处理正负前不能将字符数组反转或者转成整型数组。
int JudgePorN(char arrch1[], char arrch2[])//1代表负,0代表正
{
if (arrch1[0] == '-' && arrch2[0] != '-')
{
arrch1[0] = '0';//将 - (负号)替换为符号0,防止对后面的计算有影响,符号0在后面的计算中不会有影响,因为零会在后面作为前导零去除。注意是'0',如果写成数字0就不对了,数字0是\0的ASIIC码,写成数字0会被转换成\0,\0是字符串的结束符
return 1;
}
else if (arrch1[0] != '-' && arrch2[0] == '-')
{
arrch2[0] = '0';
return 1;
}
else if (arrch1[0] == '-' && arrch2[0] == '-')
{
arrch1[0] = '0';
arrch2[0] = '0';
return 0;
}
return 0;
}
所以处理正负函数要在字符数组反转函数和转成整型数组函数之前调用。
四、代码实现
#include <stdio.h>
#include <string.h>
void DigitReverse(char arr[])//反转字符串,以便后续计算
{
int length = (int)strlen(arr);
for (int i = 0; i < length / 2; i++)
{
int temp = arr[i];
arr[i] = arr[length - i - 1];
arr[length - i - 1] = temp;
}
}
void StringTranstoNumber(char arrchar[],int arrnum[])//将字符数组转换为数字数组
{
int length = (int)strlen(arrchar);
for (int i = 0; i < length; i++)
{
arrnum[i] = arrchar[i] - '0';
}
}
void Calculate(int num1[], int num2[], int numres[], int len1, int len2)
{
// 核心乘法算法
for (int i = 0; i < len1; i++)
{
for (int j = 0; j < len2; j++)
{
numres[i + j] += num1[i] * num2[j];
numres[i + j + 1] += numres[i + j] / 10;
numres[i + j] = numres[i + j] % 10;
}
}
}
void BigNumMul(char arrch1[], char arrch2[], int res[],int len1,int len2)
{
DigitReverse(arrch1);
DigitReverse(arrch2);
int num1[505] = {0};
int num2[505] = {0};
StringTranstoNumber(arrch1,num1);
StringTranstoNumber(arrch1,num2);
Calculate(num1,num2,res,len1,len2);//计算结果
}
void Print(int resnum[],int lengthmax)//打印函数
{
int i = lengthmax;
while (i > 0 && resnum[i] == 0)//去除前导零,因为是按最大位数算的,可能有前导零
{
i--;
}
for (; i >= 0; i--)
{
printf("%d",resnum[i]);
}
}
int JudgePorN(char arrch1[], char arrch2[])//1代表负,0代表正
{
if (arrch1[0] == '-' && arrch2[0] != '-')
{
arrch1[0] = '0';//将 - (负号)替换为符号0,防止对后面的计算有影响,符号0在后面的计算中不会有影响,因为零会在后面作为前导零去除。注意是'0',如果写成数字0就不对了,数字0是\0的ASIIC码,写成数字0会被转换成\0,\0是字符串的结束符
return 1;
}
else if (arrch1[0] != '-' && arrch2[0] == '-')
{
arrch2[0] = '0';
return 1;
}
else if (arrch1[0] == '-' && arrch2[0] == '-')
{
arrch1[0] = '0';
arrch2[0] = '0';
return 0;
}
return 0;
}
int main()
{
char ch1[505] = "0";
char ch2[505] = "0";
scanf("%s",ch1);
scanf("%s",ch2);
int res[1010] = {0};
int len1 = (int)strlen(ch1);
int len2 = (int)strlen(ch2);
if (len1 == 1 && ch1[0] == '0')
{
printf("0");
}
else if (len2 == 1 && ch2[0] == '0')
{
printf("0");
}
else if (len1 == 1 && len2 == 1 && ch1[0] == '0' && ch2[0] == '0')
{
printf("0");
}
else//非零情况
{
int judgevalue = JudgePorN(ch1, ch2);//判断结果正负并去除字符串前面的 - (负号)
BigNumMul(ch1, ch2, res, len1, len2);
int lengthmax = len1 + len2;//乘法结果最大可能是两个乘数的位数和
if (judgevalue == 1)//结果为负
{
printf("-");
Print(res, lengthmax);
}
else//结果为正
{
Print(res, lengthmax);
}
}
}