长整数的乘法运算

概述

都知道, 计算机中存储整数是存在着位数限制的, 所以如果需要计算100位的数字相乘, 因为编程本身是不支持存储这么大数字的, 所以就需要自己实现, 当然了, 各个编程语言都有大数的工具包, 何必重复造轮子, 但我还是忍不住好奇他们是如何实现的, 虽然最终没有翻到他们的底层源码去, 但查询的路上还是让我大吃一惊, 来吧, 跟我一起颠覆你的小学数学.

长乘运算

当然, 如果自己实现这样一个大数, 用数组来存储每一位是我当前想到的方法. 那如何进行乘法运算呢? 因为用数组来存储数字, 那么数字的加法也要采用每一位进位的方式来进行, 所以下面为了方便说明算法的效率, 以一次个位数的运算视为一个运算单位.

上小学知识:

  • 4 ∗ 5 = 20 4*5=20 45=20
    • 个位数相乘, 一次运算
  • 14 ∗ 5 = ( 4 ∗ 5 ) + ( 1 ∗ 5 ) ∗ 10 = 70 14*5=(4*5)+(1*5)*10=70 145=(45)+(15)10=70
    • 2位数乘1位数, 分解后共: 2次乘法和2位数的加法, 4次运算(乘10可看做移位操作)
  • 134 ∗ 6 = ( 4 ∗ 6 ) + ( 3 ∗ 6 ) ∗ 10 + ( 1 ∗ 6 ) ∗ 100 = 804 134*6=(4*6)+(3*6)*10+(1*6)*100=804 1346=(46)+(36)10+(16)100=804
    • 3位数乘1位数, 分解后共: 3次乘法, 3位数的加法(不要看两个加号, 可以乘法运算完后做连加运算, 当然, 也可能连加之后发生溢出, 暂不考虑. 此处简化只看加法的位数即可), 6次运算.
  • 1234 ∗ 7 = ( 4 ∗ 7 ) + ( 3 ∗ 7 ) ∗ 10 + ( 2 ∗ 7 ) ∗ 100 + ( 1 ∗ 7 ) ∗ 1000 = 8638 1234*7 = (4*7) + (3*7)*10 + (2*7)*100 + (1*7)*1000 = 8638 12347=(47)+(37)10+(27)100+(17)1000=8638
    • 4位数乘1位数, 8次运算.

通过上面可总结规律, n位数乘一位数, 需要 2n 次运算. 将 n 位数乘1位数的运算称作短乘. 然后下面再看一下 n 位数乘 n 位数.

  • 14 ∗ 13 = ( 14 ∗ 3 ) + ( 14 ∗ 1 ) ∗ 10 = 182 14*13=(14*3) + (14*1)*10=182 1413=(143)+(141)10=182
    • 两位数相乘, 2次短乘, 4位数加法(99*9*10 最差情况). 共: 2 ∗ ( 2 n ) + 4 = 12 2*(2n) + 4 = 12 2(2n)+4=12 次运算
  • 132 ∗ 256 = ( 132 ∗ 6 ) + ( 132 ∗ 5 ) ∗ 10 + ( 132 ∗ 2 ) ∗ 100 = 33792 132*256=(132*6)+(132*5)*10+(132*2)*100=33792 132256=(1326)+(1325)10+(1322)100=33792
    • 三位数相乘: 3次短乘, 6位数加法(最差情况), 共: 3 ∗ ( 2 n ) + 6 = 24 3*(2n) + 6=24 3(2n)+6=24次运算.

通过上面, 总结规律, n位数相乘(长乘)的运算次数是: n ∗ ( 2 n ) + 2 n = 2 n 2 + 2 n n*(2n) + 2n = 2n^2+2n n(2n)+2n=2n2+2n 次运算. 当然, 这就是我们从小接受的进行乘法运算的方法, 所以写成这样还好, 比较合乎常理. 时间复杂度是 O(n^2)

但是, 他还可以更快么? 我以为就这样了, 是我小看了伟大的数学家. .

Karatsuba方法

由简入难, 先看一下两位数的乘法:

12*34, 为了方便初中方程未知数的思维, 我们将这两个数字拆解一下:

KaTeX parse error: No such environment: align* at position 8: \begin{̲a̲l̲i̲g̲n̲*̲}̲ 12 &= 10a+b (其…

则,

KaTeX parse error: No such environment: align* at position 8: \begin{̲a̲l̲i̲g̲n̲*̲}̲ & 12*34 \\ =& …

当化简到这里, 2位数相乘需要几次运算? 来算一下:

  • 10 ( a m + b n ) 10(am + bn) 10(am+bn) : 共2次乘法, 2位数加法, 共4次运算.
  • an 和 bm : 共2次乘法, 共2次运算
  • 剩下最外层的加法, 最差情况: ( 100 ∗ 9 ∗ 9 100*9*9 10099 4位数, 10 ∗ ( 9 ∗ 9 + 9 ∗ 9 ) 10*(9*9 + 9*9) 10(99+99) 4位数), 共4次运算
  • 则总计, 4 + 4 + 2 = 10 4+4+2=10 4+4+2=10次运算.

此时, 需要的运算次数已经较之前的12次少一些了, 但是别急, 容我把公式再变换一下.

令:

u = a n w = b m s = ( b − a ) ∗ ( m − n ) u=an \\ w=bm \\ s=(b-a)*(m-n) u=anw=bms=(ba)(mn)

公式:

100 u + ( u + w − s ) ∗ 10 + w = 100 a n + ( a n + b m − ( b − a ) ∗ ( m − n ) ) ∗ 10 + b m = 100 a n + ( a n + b m − b m + b n + a m − a n ) ∗ 10 + b m = 100 a n + ( b n + a m ) ∗ 10 + b m 100u + (u+w-s)*10+w \\ = 100an + (an + bm - (b-a) * (m-n)) *10 + bm \\ = 100an + (an + bm - bm + bn + am - an)*10 + bm \\ = 100an + (bn + am) * 10 + bm 100u+(u+ws)10+w=100an+(an+bm(ba)(mn))10+bm=100an+(an+bmbm+bn+aman)10+bm=100an+(bn+am)10+bm

是不是和上面的公式一样了呢? 是的, 那转换公式是为了什么呢? 当然是为了减少运算次数啦. 算一下:

  • 计算u : 1次运算
  • 计算w: 1次运算
  • 计算 s: 3次运算
  • 计算 u+w-s: 2位数运算, 2次运算
  • 计算最外层加法: 3位数运算, 3次运算
  • 共: 10次运算.

这和我刚才计算的不也是10次么? 不过个位数的乘法换成加法就会变快了么? 不要小看这个一次乘法运算的减少, 从上面能够看出, 乘法运算的运算次数是随位数成指数增长的, 而加法运算则随位数成线性增长, 等看了下面的多位数相乘, 你就知道减少的这一次乘法运算有什么用了.

不过下面才是重头戏, 数字多了之后, 此算法就明显比传统的快的多了.

4位数乘法

计算: 1234 ∗ 5678 1234*5678 12345678

设:

1234 = 100 a + b ( 其 中 a = 12 , b = 34 ) 5678 = 100 n + m ( 其 中 n = 56 , m = 78 ) 1234 + 5678 = ( 100 a + b ) ∗ ( 100 n + m ) 1234=100a+b (其中 a=12, b=34) \\ 5678=100n+m (其中 n=56, m=78) \\ 1234+5678 = (100a + b) * (100n + m) 1234=100a+b(a=12,b=34)5678=100n+m(n=56,m=78)1234+5678=(100a+b)(100n+m)

套用上面的公式:

令:

u = a n w = b m s = ( b − a ) ∗ ( m − n ) u=an \\ w=bm \\ s=(b-a)*(m-n) u=anw=bms=(ba)(mn)

则结果为: 10000 u + ( u + w − s ) ∗ 100 + w 10000u + (u+w-s)*100+w 10000u+(u+ws)100+w

此次进行了几次运算呢? 算一下:

  • 计算 u: 两位数乘法, 10次运算
  • 计算w: 10次运算
  • 计算s: 两位数减法两次, 一次乘法, 14次运算
  • 计算整体: 8位数相加( 99 ∗ 99 ∗ 10000 99*99*10000 999910000), 8次运算
  • 整体: 10 + 10 + 14 + 8 = 32 10+10+14+8=32 10+10+14+8=32次运算.

32次运算, 之前长乘的方式需要几次呢? 2 ∗ ( 4 ∗ 4 ) + 2 ∗ 4 = 40 2*(4*4) + 2*4=40 2(44)+24=40. 是不是少了.

也就是说, 4位数的乘法, 其中用到了3次两位数乘法, 2次两位数减法, 1次8位数加法.

8位数乘法

8位数乘法就不展开了, 直接套用4位数乘法得出的结论, 其运算次数为:

  • 3次4位数乘法: 3 ∗ 32 = 96 3*32=96 332=96
  • 2次4位数减法: 2 ∗ 4 = 8 2*4=8 24=8
  • 1次 9999 ∗ 9999 ∗ 100000000 9999*9999*100000000 99999999100000000 位数加法: 17次
  • 共: 96 + 8 + 17 = 121 96+8+17=121 96+8+17=121次运算.

原来的长乘需要几次呢? 2 ∗ ( 8 ∗ 8 ) + 2 ∗ 8 = 144 2*(8*8) + 2*8=144 2(88)+28=144次.

是不是有一种动态规划, 分而治之的感觉? 可以利用函数递归来实现.

问题

想必此算法的问题也很明显了, 为了每次都能将数字拆成左右两部分, 所以只能够计算位数是2的 n 次方的数字, 如果位数不足, 则需要在前边进行补0.

算法比较

为了比较两个算法的运算次数, 让我们忽略运算的低次幂以及常数项, 则(以下 n 为2的幂):

长乘

f ( n ) = { 1 ,   n  == 1 2 ∗ ( 2 n ) 2 , else f(n) = \begin{cases} 1, \text{ $n$ == 1} \\ 2 * (2^n)^2, \text{else} \end{cases} f(n)={1, n == 12(2n)2,else

Karatsuba:

f ( n ) = { 3 , n ==1 3 ∗ f ( n − 1 ) , else f(n) = \begin{cases} 3, \text{$n$==1} \\ 3*f(n-1), \text{else} \end{cases} f(n)={3,n==13f(n1),else

分别进行计算:

2的幂/数字位数长乘Karatsuba
2 0 = 1 2^0=1 20=111
2 1 = 2 2^1 = 2 21=283
2 10 = 1024 2^{10}=1024 210=10242097152
2 20 = 1048576 2^{20}=1048576 220=104857621990232555521162261467
2 50 = 1125899906842624 2^{50}=1125899906842624 250=11258999068426242535301200456458802993406410752239299329230617529590083
2 100 = 1267650600228229401496703205376 2^{100}=1267650600228229401496703205376 2100=12676506002282294014967032053763213876088517980551083924184682325205044405987565585670602752171792506910670443678820376588540424234035840667

可以看出来, 当数字的位数越大, 则两个算法之间的差距越明显.


有没有被颠覆的感觉? 是不是自己知道了20多年的乘法运算, 根本没有想到还有其他计算乘法的运算规则? 我也没想到, 涨见识了…

果然, 没有什么是伟大的科学家们做不到的, 这算法我看了近乎整整一天, 草稿纸废了四十张, 总算是略知一二了.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值