高精度数字的减法运算法则也已经介绍清楚了:高精度减法
接下来,按照顺序我们该介绍高精度乘法了。我们说高精度乘法其实是分为两个部分的,第一个部分是两个高精度数字相乘,另一种是高精度数字乘上一个常规数字。
我们一个一个介绍吧。首先,两个高精度数字相乘。
与高精度加减法类似,我们回顾一下小学五六年级的知识,两个整数相乘。我们假设计算45*14
的积。在数学中,你会做什么?没错,你可能会列一个竖式,然后一个一个计算。
我们使用表格尝试表述一下。
下标 | 0 | 1 | 2 |
---|---|---|---|
乘数 | 0 | 4 | 5 |
乘数 | 0 | 1 | 4 |
第一层临时结果 | 0 | 16 | 20 |
第二层临时结果 | 4 | 5 | 0 |
最终结果 | 6 | 3 | 0 |
从表格中看到,在第一层临时结果
的位置出现了16
和20
,这在数学中是不合理的。因此我们需要进行转换。将20
中的十位数部分作为进位,个位数部分留在本数位上。同样地,16
也要进行这样的操作。
我们在这里对第一层临时结果
重新进行组织。当45
与14
中的个位数字相乘,结果得到20
,数字超过10
,我们必须考虑进位问题了。但与高精度数字加法不同的是,乘法的进位会超过20
,比如说3*7
就已经达到了21
。那么进位的数值将是两个数字之积的十位数,即20/10
。除以10
是个不错的选择,可以直接获取十位数。这是进位了。那么对于留在本数位上的内容,也不再是-10
,而是%10
——获取个位数字。
那么对于第一层临时结果
,个位数字5*4
得到20
,个位数字0
留在本数位,十位数字2
作为进位前往高位。紧接着,45
的十位数字4
与14
的个位数字4
相乘,结果为16
。但16
并不完整,它还需要加上进位才可以,结果变成18
。同样地,18
的个位数字保留本数位,十位数字作为进位。
表格会清晰地描述这一过程。
下标 | 0 | 1 | 2 |
---|---|---|---|
乘数 | 0 | 4 | 5 |
乘数 | 0 | 1 | 4 |
第一层进位 | (4*4+2)/10 | 4*5/10 | 0 |
第一层临时结果 | 1 | (4*4+2)%10 | 4*5%10 |
这样关于进位的问题我们算是了解了。但我们还需要思考,多位数相乘时,两个乘数的每一位都是需要相乘的。这用代码如何表示呢?使用两层循环是一个不错的选择。一个循环控制一个乘数。
//假设a1,a2均已定义且长度不超过100
//根据循环的嵌套,外层循环i增加1,内层循环j会从0~100走一轮
for(int i = 0;i < 100; ++i)
{//变量i用来遍历a1的每一个数字
for(int j = 0; j < 100; ++j)
{//变量j用来遍历a2的每一个数字
//这里将用来计算乘积
}
}//循环此处结束,所有的数字都会乘一遍
终于有点头目了,还真是不容易。接下来,我们还需要考虑两个数相乘结果的问题。按理来说,我们需要一个整数数组来存放乘积,那么这个数组可以叫做result
,这没问题。但数组大小呢?还应该是100
个吗?这是不够的!!我们假设100*100
结果为10000
,很显然,3
位数乘上3
位数结果是5
位数,而这个3
位数确实最小的3
位数。也就是说,2
位数乘以2
位数的结果没有达到5
位数,也就是4
位数。因此,我们可以说,两个2
位数相乘最多产生4
位数。同理,result
的大小应该为200
。我想大家现在没有什么疑义了吧。
int result[200] = {}; //存放结果
接下来,我们必须思考的一个问题,当下标为i
的数字和下标为j
的数字相乘时,其个位数字应该放在什么位置?或者说,我们前面说的本数位
怎么确定呢?
谈及这个问题,我们不得不重新思考45*14
这个实例。我们将它们使用整数数组表示出来,并逆向存放。这是为了模拟高精度乘法的过程。
下标 | 0 | 1 | 2 |
---|---|---|---|
乘数 | 5 | 4 | 0 |
乘数 | 4 | 1 | 0 |
第一层临时结果 | 20 | 16 | 0 |
第二层临时结果 | 0 | 5 | 4 |
最终结果 | 0 | 3 | 6 |
45
中下标为0
的5
与14
中下标为0
的4
相乘,结果本数位指向下标为0
的位置。45
中下标为1
的4
与14
中下标为0
的4
相乘,结果本数位指向下标为1
的位置。从这两步来看,貌似本数位的结果与45
中的下标有关系。
45
中下标为0
的5
与14
中下标为1
的1
相乘,结果本数位指向下标为1
的位置。45
中下标为1
的4
与14
中下标为1
的1
相乘,结果本数位指向下标为2
的位置。从这两步来看,本数位又好像与14
中的下标有关系。
其实,结果下标与两个整数的下标都有直接关系,当下标为i
的数字与下标为j
的数字相乘时,其个位数字应放在结果数组中下标为i+j
的位置,而进位为i+j+1
的位置。
大家可以使用上述实例进行验证。
有了这样的结论,程序将变得完整。我们尝试使用程序来描述一下。
//假设a1,a2已经定义,并逆向存放在整数数组中
//假设a1,a2的元素个数不超过100
// a1 {6, 8, 4, 9, 3, 0, 2, 8, 7}
// a2 {5, 0, 1, 2, 4, 5, 6, 1, 2}
void multiplication(int a1[], int a2[])
{
int result[200] = {}; //存放结果
for(int i = 0;i < 100; ++i)
{//使用变量i访问a1的所有数字
for(int j = 0; j < 100; ++j)
{//使用变量j访问a2的所有数字
int num = result[i + j]; //保存进位
num += a1[i] * a2[j]; //加上两个数字的乘积
//乘积可能会达到进位的情况
if(num >= 10)
{
//i + j + 1 为进位的位置
//将10位数字存放在进位处
//但需要考虑的是,原位置可能存在数字,因此采用 += 比较合适
result[i + j + 1] += num / 10;
num %= 10; //获得个位数字,放在本数位上
}
result[i + j] = num; //个位数字存放在本数位上
}
}//经过两层循环,a1和a2的每一个数字都会相乘
//去前导0
int k = 200 - 1; //从result的最后一个下标开始
while(result[k] == 0 && k > 0)
--k;
for(int i = k; i >= 0; --i)
cout << result[i];
}
完整的程序算是实现了。但其中有一个位置我觉得还是有必要解释一下。
result[i + j + 1] += num / 10;
针对这个+=
的含义,我们有必要弄清楚。这就需要我们重新回顾乘法法则。这次我们假设45*45
绘制一个新的表格。在这个新表格中,我将第二层临时结果
和最终结果
没有填写。
下标 | 0 | 1 | 2 | 3 |
---|---|---|---|---|
乘数 | 5 | 4 | 0 | 0 |
乘数 | 5 | 4 | 0 | 0 |
第一层临时结果 | 5 | 2 | 2 | 0 |
第二层临时结果 | ||||
最终结果 |
在第一层临时结果
中,我们可以很清晰地了解到,下标为2
的位置已经存在元素2
了。当进行第二层临时结果
求解时,我们一步一步推断。
十位数字4
需要和45
中的每一位相乘。首先相乘的是个位数字
,得到结果是20
,此时是需要进位的,进位的位置是下标为2
的位置——十位数字4
下标为1
,45
中个位数字下标为0
,根据上述描述,本数位为1+0
,进位为1+0+1
。而下标为2
的位置已经存在2
,我们只能将进位
加到2
上,而不是直接赋值。
我想这样才能准确的描述清楚。
最后,我们再简单介绍一下,当我们遇到连乘的情况,例如:684930287*501245612*894571356
我们并不能直接在函数内进行输出,那么我们该如何处理呢?答案肯定是将结果传递到函数外。前几次遇到这种情况,我们将结果之间保存在加数
或者被减数
中,但对于乘法来说,我们就没办法直接尽进行运算得到了。原因是,乘法法则中存在多重循环,直接运算可能会导致数据不准确。
我们可以通过在运算结束之后将结果拷贝到其中一个乘数中来。
//假设a1,a2已经定义,并且逆向存放着整数
//假设a1,a2的大小均为300,并且其中每一个高精度数字不超过100
//高精度乘法运算
void multiplication(int a1[], int a2[])
{
int result[300] = {}; //存放结果
for(int i = 0; i < 100; ++i)
{//使用下标i访问a1的所有数字
for(int j = 0; j < 100; ++j)
{//使用下标j访问a2的所有数字
int num = result[i + j]; //保存低位进位
num += a1[i] * a2[j];
if(num >= 10) //可能达到进位的要求
{
result[i + j + 1] += num / 10; //进位
num %= 10; //获取个位数字
}
result[i + j] = num; //个位数字保留在本数位上
}
}//经过两层循环,a1和a2的每一个数字都会相乘
//将结果拷贝到乘数中
for(int i = 0; i < 300; ++i)
a1[i] = reuslt[i];
}
//下面补充主函数的内容,以帮助大家理解
int main()
{
string nums[3] = {"684930287", "501245612", "894571356"};
//三个100位数字相乘,其结果可能达到300位
int result[300] = {1}; //存放结果,将第一个元素初始化为1,乘法才不会结果为0
for(int i = 0;i < 3; ++i)
{
string s = nums[i];
int a[300] = {}; //用来存放乘数
for(int j = 0; j < s.size(); ++j)
a[j] = s[s.size() - 1 - j] - '0';
multiplication(result, a);
}
int k = 300 - 1;
while(result[k] == 0 && k > 0)
--k;
for(int i = k; i >= 0; --i)
cout << result[i];
}
两个高精度数字相乘的运算法则终于描述结束了。下期我们将对于高精度数字与常规数字相乘进行描述。