前一章节我们了解了:高精度数字乘法的规则——两个高精度数字相乘
这一篇我们将尝试描述一个高精度数字与一个常规数字相乘的解决方案。你可能也会产生好奇,毕竟使用整数数组表示数字的方式是不论高精度还是常规数字的。在这里还是举一个实例来表明我的含义。例如:数字1234
使用int
类型,甚至short
类型完全能够表示的,但同样的,我们使用整数数组表示,也并不能认为是错误的。
下标 | 0 | 1 | 2 | 3 |
---|---|---|---|---|
数字 | 4 | 3 | 2 | 1 |
对于这样的形式,我想,把它当作高精度的一种也是可以理解的。这样的话,高精度数字乘上一个常规数字就可以转化为两个高精度数字相乘了。这是一个好方法。
但我觉得这个方法有些麻烦。原因是我们需要使用循环语句对数字进行分解,这会消耗一些时间。当数字较多时,分解的时间就比较长了。我们必须还一种方法。
我将用这个例子来说明我的想法。
计算阶乘。
阶乘就是从1开始一直累乘,直到这个数字本身。如:5! = 1 * 2 * 3 * 4 * 5 结果为之120
当计算的数字超过20时,结果就会超过long long 类型能表示的范围了。
请计算30的阶乘。
如果这个数字没有超过long long
所表示的范围,我们完全可以写成下面的代码。
int main()
{
int n = 0;
cin >> n;
long long sum = 1;
for(int i = 1; i <= n; ++i)
sum *= n;
cout << sum << endl;
}
显然在这道问题中,这个代码是不合适的。并且这个程序中,需要不断的乘上一个新数字。我们不得不考虑将数字转换为整数数组所付出的时间了。
我们不得不正视一个高精度数字乘上常规数字的内容。
首先,我们需要承认一个事实,常规数字可以直接进行运算,而无须拆解成单个数字。这句话你可能会感到莫名其妙。我们进一步解释。我将使用这样的实例,684930287*1234
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
高精度数字 | 7 | 8 | 2 | 0 | 3 | 9 | 4 | 8 | 6 |
常规数字 | 1234 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
这是一个小技巧。我们将完整的1234
当成一个数字,直接使用乘法,使得1234
乘上高精度数字的每一位。这样得到的结果是有效的。如果这样的表达方式,你还是感到疑惑,我们不妨把这个数字改成100
。
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
高精度数字 | 7 | 8 | 2 | 0 | 3 | 9 | 4 | 8 | 6 | 0 | 0 |
常规数字 | 100 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
结果 | 0 | 0 | 7 | 8 | 2 | 0 | 3 | 9 | 4 | 8 | 6 |
我想这样是清晰的。每一个数字都向右移动了两位,因为每个数字都乘上了100
。我们使用同样的方式处理高精度数字与常规数字相乘的内容。你是否会写出这样的代码?
//假设a1已经定义,并且逆向存放整数
//假设a1的大小为100
//高精度数字乘上常规数字
void multiplication(int a1[], int num)
{
int result[200] = {}; //保存结果,由于不知道num的元素个数,因此,保留更多位置
for(int i = 0;i < 100; ++i)
{//通过变量i访问a1中所有数字
int tmp = result[i]; //保存进位
tmp += a1[i] * num;
//接下来将对tmp进行处理,tmp可能会达到进位的情况
}
}
如果你有仔细观察,代码中对于tmp
的处理并没有完整编写出来。我觉得这个代码是比较重要的。我们需要考虑到num
的长度我们是不确定的。这一点,我再次声明。因此,a1
中的某一个数字乘上num
之后的结果将有多少位数,我们也是不确定的。这让我们很为难。我们需要考虑进位,我们需要将进位填入,就像高精度加法一样result[i + 1] += 1
。
如果进位不确定该怎么办呢?比如说,num
的数值是1234
,而a1
中某一位置数字为2
,得到结果为2468
,留在本位上的只能是最后一位8
。其他的246
均为进位。很显然,这个进位是需要存放到高3
位中的,分别存放2
、4
、6
。情况显得有点麻烦。
如果能够将进位只放在某一个位置就好了,我们每次都将进位保存在那里。我们希望得到这样的结果。我们查看这样的例子。
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
高精度数字 | 7 | 8 | 2 | 0 | 3 | 9 | 0 |
常规数字 | 100 | 0 | 0 | 0 | 0 | 0 | 0 |
进位 | 0 | 70 | 80 | 20 | 0 | 30 | 90 |
计算方式 | 0 | 0 | (70/10+80)%10 | (80/10+20)%10 | (20/10+0)%10 | 30%10 | (30/10+90)%10 |
结果 | 0 | 0 | 7 | 8 | 2 | 0 | 3 |
这个表格中,我们需要注意一点,表格并没有给出完整的计算结果。下标为6
的进位上,还保留了数字90
,进位到下一个位置。也就是说,如果存在下标为7
时,结果栏
会存在一个9
。
通过这样的描述,我们也能看到进位即使是多位,仍然可以保存在高一位上。但对于最高位来说,我们需要处理最高位进位为多位的问题。就例如表格中的90
,需要分解到更高的数位上。
那么具体最高位该在什么位置呢?这与高精度数字的位数有关。表格中也有所体现。高精度数字在下标为5
的位置结束,那么它的最高位也就是下标为5
的位置,这与它的数位6位数
有关系。
我们尝试描述这样的情形。
//假设a1已经定义,并且逆向存放整数数组
//假设a1中的元素个数为length位
//假设num是一个常规正整数
//一个高精度数字与常规数字的乘积
void multiplication(int a1[], int &length, unsigned int num)
{
//并不需要太大的空间,我们假设a1为100位,而常规整数最多不超过9位数字
int result[110] = {};
int carry = 0; //表示进位,进位会向高位传递,不论进位的具体数位,即使是多位
//在循环中,这一次的进位carry会计算进下一次的结果
for(int i = 0;i < length; ++i)
{
int num = carry; //保存当前进位
num += a1[i] * num; //进位加上两个数的乘积
//可能会产生进位,也可能没有进位。进位有可能为多位
result[i] = num % 10; //保留个位数字在本数位上
carry = num / 10; //计算进位,当num < 10时, 进位为0。超过10的部分将作为进位
}//经过循环,a1中的所有数字都会与num相乘,但最高位还存在进位carry需要处理
//处理最高位进位carry内容
//当进位为1234,我们需要将几个数字分别放在不同的数位上
while(carry != 0)
{
result[length] = carry % 10; //将进位的个位数字保留下来
carry /= 10; //减少进位
++length; //保留进位之后,a1的长度会产生变化
}
//输出结果
int k = length - 1; //结果的长度存放在length中
while(result[k] == 0 && k > 0)
--k;
for(int i = k; i >= 0; --i)
cout << result[i];
}
值得一提的是,我在函数的参数中,使用了&length
,在这里表示引用传参,我们希望使用这个函数,在函数外也可以了解length
的数值发生了变化。
通过这样的处理,关于高精度整数与常规数字相乘的结果,我想已经足够清晰了。