长整数的乘法运算 概述 长乘运算 Karatsuba方法 算法比较
都知道, 计算机中存储整数是存在着位数限制的, 所以如果需要计算100位的数字相乘, 因为编程本身是不支持存储这么大数字的, 所以就需要自己实现, 当然了, 各个编程语言都有大数的工具包, 何必重复造轮子, 但我还是忍不住好奇他们是如何实现的, 虽然最终没有翻到他们的底层源码去, 但查询的路上还是让我大吃一惊, 来吧, 跟我一起颠覆你的小学数学.
长乘运算
当然, 如果自己实现这样一个大数, 用数组来存储每一位是我当前想到的方法. 那如何进行乘法运算呢? 因为用数组来存储数字, 那么数字的加法也要采用每一位进位的方式来进行, 所以下面为了方便说明算法的效率, 以一次个位数的运算视为一个运算单位.
说明一下, 以下的计算步骤计数仅是我个人理解, 与网上其他文章所写不太一样. 仅代表个人观点.
上小学知识:
-
(4*5=20)
- 个位数相乘, 一次运算
-
(14*5=(4*5)+(1*5)*10=70)
- 2位数乘1位数, 分解后共: 2次乘法和2位数的加法, 4次运算(乘10可看做移位操作)
-
(134*6=(4*6)+(3*6)*10+(1*6)*100=804)
- 3位数乘1位数, 分解后共: 3次乘法, 3位数的加法(不要看两个加号, 可以乘法运算完后做连加运算, 当然, 也可能连加之后发生溢出, 暂不考虑. 此处简化只看加法的位数即可), 6次运算.
-
(1234*7 = (4*7) + (3*7)*10 + (2*7)*100 + (1*7)*1000 = 8638)
- 4位数乘1位数, 8次运算.
通过上面可总结规律, n位数乘一位数, 需要 2n 次运算. 将 n 位数乘1位数的运算称作短乘
. 然后下面再看一下 n 位数乘 n 位数.
-
(14*13=(14*3) + (14*1)*10=182)
- 两位数相乘, 2次短乘, 4位数加法(99*9*10 最差情况). 共: (2*(2n) + 4 = 12) 次运算
-
(132*256=(132*6)+(132*5)*10+(132*2)*100=33792)
- 三位数相乘: 3次短乘, 6位数加法(最差情况), 共: (3*(2n) + 6=24)次运算.
通过上面, 总结规律, n位数相乘(长乘
)的运算次数是: (n*(2n) + 2n = 2n^2+2n) 次运算. 当然, 这就是我们从小接受的进行乘法运算的方法, 所以写成这样还好, 比较合乎常理. 时间复杂度是 O(n^2)
但是, 他还可以更快么? 我以为就这样了, 是我小看了伟大的数学家. .
Karatsuba方法
由简入难, 先看一下两位数的乘法:
12*34, 为了方便初中方程未知数的思维, 我们将这两个数字拆解一下:
则,
当化简到这里, 2位数相乘需要几次运算? 来算一下:
- (10(am + bn)) : 共2次乘法, 2位数加法, 共4次运算.
- an 和 bm : 共2次乘法, 共2次运算
- 剩下最外层的加法, 最差情况: ((100*9*9) 4位数, (10*(9*9 + 9*9)) 4位数), 共4次运算
- 则总计, (4+4+2=10)次运算.
此时, 需要的运算次数已经较之前的12次少一些了, 但是别急, 容我把公式再变换一下.
令:
公式:
是不是和上面的公式一样了呢? 是的, 那转换公式是为了什么呢? 当然是为了减少运算次数啦. 算一下:
- 计算
u
: 1次运算 - 计算
w
: 1次运算 - 计算
s
: 3次运算 - 计算
u+w-s
: 2位数运算, 2次运算 - 计算最外层加法: 3位数运算, 3次运算
- 共: 10次运算.
这和我刚才计算的不也是10次么? 不过个位数的乘法换成加法就会变快了么? 不要小看这个一次乘法运算的减少, 从上面能够看出, 乘法运算的运算次数是随位数成指数增长的, 而加法运算则随位数成线性增长, 等看了下面的多位数相乘, 你就知道减少的这一次乘法运算有什么用了.
不过下面才是重头戏, 数字多了之后, 此算法就明显比传统的快的多了.
4位数乘法
计算: (1234*5678)
设:
套用上面的公式:
令:
则结果为: (10000u + (u+w-s)*100+w)
此次进行了几次运算呢? 算一下:
- 计算
u
: 两位数乘法, 10次运算 - 计算
w
: 10次运算 - 计算
s
: 两位数减法两次, 一次乘法, 14次运算 - 计算整体: 8位数相加((99*99*10000)), 8次运算
- 整体: (10+10+14+8=32)次运算.
32次运算, 之前长乘
的方式需要几次呢? (2*(4*4) + 2*4=40). 是不是少了.
也就是说, 4位数的乘法, 其中用到了3次两位数乘法, 2次两位数减法, 1次8位数加法.
8位数乘法
8位数乘法就不展开了, 直接套用4位数乘法得出的结论, 其运算次数为:
- 3次4位数乘法: (3*32=96)次
- 2次4位数减法: (2*4=8)次
- 1次 (9999*9999*100000000) 位数加法: 17次
- 共: (96+8+17=121)次运算.
原来的长乘
需要几次呢? (2*(8*8) + 2*8=144)次.
是不是有一种动态规划, 分而治之的感觉? 可以利用函数递归来实现.
问题
想必此算法的问题也很明显了, 为了每次都能将数字拆成左右两部分, 所以只能够计算位数是2的 n 次方的数字, 如果位数不足, 则需要在前边进行补0.
算法比较
为了比较两个算法的运算次数, 让我们忽略运算的低次幂以及常数项, 则(以下 n 为2的幂):
长乘
Karatsuba:
分别进行计算:
2的幂/数字位数 | 长乘 | Karatsuba |
---|---|---|
(2^0=1) | 1 | 1 |
(2^1 = 2) | 8 | 3 |
(2^{10}=1024) | 2097152 | |
(2^{20}=1048576) | 2199023255552 | 1162261467 |
(2^{50}=1125899906842624) | 2535301200456458802993406410752 | 239299329230617529590083 |
(2^{100}=1267650600228229401496703205376) | 3213876088517980551083924184682325205044405987565585670602752 | 171792506910670443678820376588540424234035840667 |
可以看出来, 当数字的位数越大, 则两个算法之间的差距越明显.
有没有被颠覆的感觉? 是不是自己知道了20多年的乘法运算, 根本没有想到还有其他计算乘法的运算规则? 我也没想到, 涨见识了...
果然, 没有什么是伟大的科学家们做不到的, 这算法我看了近乎整整一天, 草稿纸废了四十张, 总算是略知一二了.