大數與小數的求和算法


原文首發於我的微信公眾號:GeekArtT .

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

而如果一定要做出這樣的大數與小數的求和,一個直觀想法就是:大數部分和小數部分的高位相加,將剩余的小數部分作為單獨的“補全”部分相加。這種直觀想法的官方名稱叫做Kahan求和法。 

假設當前的浮點數變量可以保存6位的數值。那么,數值12345與1.234相加的理論值應該是12346.234。但由於當前只能保存6位數值,這個正確的理論值會被截斷為12346.2,這就出現了0.034的誤差。當有很多這樣的大數與小數相加時,截斷誤差就會逐步累積,導致最后的計算結果出現大的偏差。

 

Kahan算法:

def KahanSum(input):
    var sum = 0.0
    var c = 0.0
    for i = 1 to input.length do
        var y = input[i] - c    // Initially, c is zero; then it compensates previous accuracy. 
        var t = sum + y         // low-order digits of y are lost
        c = (t - sum) - y       // recover the low-order digits of y, with negative symbol
        sum = t
    next i

    return sum

在上述偽代碼中,變量c表示的即是小數的補全部分compensation,更嚴格地說,應該是負的補全部分。隨着這個補全部分的不斷積累,當這些截斷誤差積累到一定量級,它們在求和的時候也就不會被截斷了,從而能夠相對好地控制整個求和過程的精度。 

以下,先用一個具體的理論例子來說明。比如,用10000.0 + pi + e來說明,我們依舊假設浮點型變量只能保存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                      // low-order digits have lost

c = (10003.1 - 10000.0) - 3.14159
  = 3.10000 - 3.14159
  = - (.0415900)                 // recover the negative parts of compensation errors

sum = 1003.1

 

第二次求和:

y = 2.71828 - (-.0415900)
  = 2.75985    // Add previous compensated parts to current small number

t = 10003.1 + 2.75987
  = 10005.85987
  = 10005.9    // As the low-order digits have been accumulated large enought, it won't be canceled by big number

c = (10005.9 - 10003.1) - 2.75987
  = 2.80000 - 2.75987
  = .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。

 

Python Code:

summ = 1000000000

for indx in xrange(1000000):
    summ += 0.000001

summ -= 1000000000

print summ

運行后,可以得到結果是0.953674316406。可以看到,在10^6次求和后,截斷誤差的累積量已經非常可觀了。 

如果我們用Kahan求和法來做改進,可以得到:

 

Python Code with Kahan method:

summ = 1000000000
c = 0.0

for indx in xrange(1000000):
    y = 0.000001 - c
    t = summ + y
    c = (t - summ) - y
    summ = t

summ -= 1000000000

print summ

運行后,我們可以欣喜地看到正確結果:1.0。

 

 

如果你喜歡我的文章或分享,請長按下面的二維碼關注我的公眾號,謝謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM