算法之高精度(五)

高精度数字的减法运算法则也已经介绍清楚了:高精度减法
接下来,按照顺序我们该介绍高精度乘法了。我们说高精度乘法其实是分为两个部分的,第一个部分是两个高精度数字相乘,另一种是高精度数字乘上一个常规数字。
我们一个一个介绍吧。首先,两个高精度数字相乘
与高精度加减法类似,我们回顾一下小学五六年级的知识,两个整数相乘。我们假设计算45*14的积。在数学中,你会做什么?没错,你可能会列一个竖式,然后一个一个计算。
我们使用表格尝试表述一下。

下标012
乘数045
乘数014
第一层临时结果01620
第二层临时结果450
最终结果630

从表格中看到,在第一层临时结果的位置出现了1620,这在数学中是不合理的。因此我们需要进行转换。将20中的十位数部分作为进位,个位数部分留在本数位上。同样地,16也要进行这样的操作。
我们在这里对第一层临时结果重新进行组织。当4514中的个位数字相乘,结果得到20,数字超过10,我们必须考虑进位问题了。但与高精度数字加法不同的是,乘法的进位会超过20,比如说3*7就已经达到了21。那么进位的数值将是两个数字之积的十位数,即20/10。除以10是个不错的选择,可以直接获取十位数。这是进位了。那么对于留在本数位上的内容,也不再是-10,而是%10——获取个位数字。
那么对于第一层临时结果,个位数字5*4得到20,个位数字0留在本数位,十位数字2作为进位前往高位。紧接着,45的十位数字414的个位数字4相乘,结果为16。但16并不完整,它还需要加上进位才可以,结果变成18。同样地,18的个位数字保留本数位,十位数字作为进位。
表格会清晰地描述这一过程。

下标012
乘数045
乘数014
第一层进位(4*4+2)/104*5/100
第一层临时结果1(4*4+2)%104*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这个实例。我们将它们使用整数数组表示出来,并逆向存放。这是为了模拟高精度乘法的过程。

下标012
乘数540
乘数410
第一层临时结果20160
第二层临时结果054
最终结果036

45中下标为0514中下标为04相乘,结果本数位指向下标为0的位置。45中下标为1414中下标为04相乘,结果本数位指向下标为1的位置。从这两步来看,貌似本数位的结果与45中的下标有关系。
45中下标为0514中下标为11相乘,结果本数位指向下标为1的位置。45中下标为1414中下标为11相乘,结果本数位指向下标为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绘制一个新的表格。在这个新表格中,我将第二层临时结果最终结果没有填写。

下标0123
乘数5400
乘数5400
第一层临时结果5220
第二层临时结果
最终结果

第一层临时结果中,我们可以很清晰地了解到,下标为2的位置已经存在元素2了。当进行第二层临时结果求解时,我们一步一步推断。
十位数字4需要和45中的每一位相乘。首先相乘的是个位数字,得到结果是20,此时是需要进位的,进位的位置是下标为2的位置——十位数字4下标为145中个位数字下标为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];
}

两个高精度数字相乘的运算法则终于描述结束了。下期我们将对于高精度数字与常规数字相乘进行描述。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值