長整數的乘法運算

概述

都知道, 計算機中存儲整數是存在着位數限制的, 所以如果需要計算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, 爲了方便初中方程未知數的思維, 我們將這兩個數字拆解一下:

\[\begin{align*} 12 &= 10a+b (其中 a=1, b=2) \\ 34 &= 10n+m (其中 n=3, m=4) \end{align*} \]

則,

\[\begin{align*} & 12*34 \\ =& (10a + b) * (10n+m) \\ =& 100an + 10am + 10bn + bm \\ =& 100an + 10(am + bn) + bm \end{align*} \]

當化簡到這裏, 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次少一些了, 但是別急, 容我把公式再變換一下.

令:

\[\begin{align*} u&=an \\ w&=bm \\ s&=(b-a)*(m-n) \end{align*} \]

公式:

\[\begin{align*} & 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 \end{align*} \]

是不是和上面的公式一樣了呢? 是的, 那轉換公式是爲了什麼呢? 當然是爲了減少運算次數啦. 算一下:

  • 計算u : 1次運算
  • 計算w: 1次運算
  • 計算 s: 3次運算
  • 計算 u+w-s: 2位數運算, 2次運算
  • 計算最外層加法: 3位數運算, 3次運算
  • 共: 10次運算.

這和我剛纔計算的不也是10次麼? 不過個位數的乘法換成加法就會變快了麼? 不要小看這個一次乘法運算的減少, 從上面能夠看出, 乘法運算的運算次數是隨位數成指數增長的, 而加法運算則隨位數成線性增長, 等看了下面的多位數相乘, 你就知道減少的這一次乘法運算有什麼用了.

不過下面纔是重頭戲, 數字多了之後, 此算法就明顯比傳統的快的多了.

4位數乘法

計算: \(1234*5678\)

設:

\[\begin{align*} 1234&=100a+b (其中 a=12, b=34) \\ 5678&=100n+m (其中 n=56, m=78) \\ 1234+5678 &= (100a + b) * (100n + m) \end{align*} \]

套用上面的公式:

令:

\[\begin{align*} u&=an \\ w&=bm \\ s&=(b-a)*(m-n) \end{align*} \]

則結果爲: \(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的冪):

長乘

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

Karatsuba:

\[f(n) = \begin{cases} 3, \text{$n$==1} \\ 3*f(n-1), \text{else} \end{cases} \]

分別進行計算:

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多年的乘法運算, 根本沒有想到還有其他計算乘法的運算規則? 我也沒想到, 漲見識了...

果然, 沒有什麼是偉大的科學家們做不到的, 這算法我看了近乎整整一天, 草稿紙廢了四十張, 總算是略知一二了.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章