一個超大數與一個超小數求和,會發生什麼呢

背景

在計算機求和的過程當中,一個大數和小數的相加會由於浮點數的有限精度,而致使截斷偏差的出現。因此在構建計算網格的時候,都要極力避免這樣情形的發生,將計算統一在相對較近的數量級上。因此,當須要對一系列的數值作加法時,一個好的技巧是將這些數由大到小作排列,再逐個相加。python

而若是必定要作出這樣的大數與小數的求和,一個直觀想法就是:大數部分和小數部分的高位相加,將剩餘的小數部分做爲單獨的「補全」部分相加。這種直觀想法的官方名稱叫作__Kahan求和法__。算法

假設當前的浮點數變量能夠保存6位的數值。那麼,數值123451.234相加的理論值應該是12346.234。但因爲當前只能保存6位數值,這個正確的理論值會被截斷爲12346.2,這就出現了0.034的偏差。當有不少這樣的大數與小數相加時,截斷偏差就會逐步累積,致使最後的計算結果出現大的誤差。code

Kahan算法:

先上僞代碼(看不懂不要着急,後面要逐一解釋):ci

def KahanSum(input):
    var sum = 0.0
    var c = 0.0
    
    for i = 1 to input.length do
        var y = input[i] + c    # 在新的輸入中,加上以前累積的補所有分.
        var t = sum + y         # 此時,將這個新的輸入加入到求和變量sum裏時,會形成低位部分被截斷
        c = y - (t - sum)       # 恢復上一步求和時被截斷的低位部分,累積進入補全變量
        sum = t                 
    next i

    return sum

在上述僞代碼中,變量c表示的便是小數的補所有分compensation,更嚴格地說,應該是__負的__補所有分。隨着這個補所有分的不斷積累,當這些截斷偏差積累到必定量級,它們在求和的時候也就不會被截斷了,從而可以相對好地控制整個求和過程的精度。input

如下,先用一個具體的理論例子來講明。好比,用it

$$10000.0 + \pi + \mathcal{e}$$io

來講明。class

咱們依舊假設浮點型變量只能保存6位數值。此時,具體寫出求和算式應該是:基礎

$$10000.0 + 3.14159 + 2.71828$$變量

它們的理論結果應該是10005.85987,約等於10005.9

但因爲截斷偏差,第一次求和

$$10000.0 + 3.14159$$

只能獲得結果10003.1;這個結果再與2.71828相加,獲得10005.81828,被截斷爲10005.8。此時結果就相差了0.1

運用Kahan求和法,咱們的運行過程是(記住,咱們的浮點型變量__保存6位數值__),

第一次求和:

y = 3.14159 + 0.00000

t = 10000.0 + 3.14159
  = 10003.14159
  = 10003.1                      # 低位部分被截斷,丟失

c = 3.14159 - (10003.1 - 10000.0) 
  = 3.14159 - 3.10000
  = (.0415900)                 # 恢復丟失的截斷部分

sum = 1003.1

第二次求和:

y = 2.71828 + (.0415900)
  = 2.75985    # 將上一步的補所有分增長在新的輸入上

t = 10003.1 + 2.75987
  = 10005.85987
  = 10005.9    # 當低位部分被累積得足夠大後,它就不會被大數的求和所截斷消失

c = 2.75987 - (10005.9 - 10003.1)
  = 2.75987 - 2.80000
  = -.040130

sum = 10005.9

實例

以上是理論分析。下面用一個能夠運行的Python代碼作示範,方便感興趣的朋友作研究。

這個例子曾經出現於Google的首席科學家_Vincent Vanhoucke_在Udacity上開設的__Deep Learning__課程。

這個求和算式是:在$10^9$的基礎上,加上$10^{-6}$,總共重複$10^6$次這個加法,再減去$10^9$,即

$$10^9 + 10^6*10^{-6} - 10^9$$

理論值顯然應該爲1

summ = 10**9

for indx in range(10**6):
    summ += 10**(-6)

summ -= 10**9

print(summ)

# output: 0.95367431640625

運行後,能夠貌似驚訝地看到結果居然不是1,而是0.95367431640625

這能夠說明,在$10^6$次求和後,截斷偏差的累積量已經很是可觀了。

運用Kahan算法作改進

若是咱們用Kahan求和法來作改進,能夠獲得:

summ = 10**9

c = 0.0

for indx in range(10**6):
    y = 10**(-6) + c
    t = summ + y
    c = y - (t - summ)
    summ = t

summ -= 10**9

print(summ)

# output: 1.0

運行後,咱們能夠欣喜地看到正確結果:1.0。

相關文章
相關標籤/搜索