討論區快速選單
知識庫快速選單
政府補助!資策會APP就業班 我的IT職涯該如何規劃 ? 傑米的攝影旅遊筆記
[ 回上頁 ] [ 討論區發言規則 ]
使用浮點數最最基本的觀念
更改我的閱讀文章字型大小
作者 : shene(冼鏡光) 人氣指數超過30000點
[ 貼文 135 | 人氣 42288 | 評價 1420 | 評價/貼文 10.52 | 送出評價 0 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2007/11/23 上午 11:09:12
使用浮點數最最基本的觀念
冼鏡光(2007年11月)

不知道各位在程式中使用浮點(或實)數的機會有多大,也不知道各位的程式有否因為浮點數問題而不能產生正確結果的困擾,也不知道是否有程式因為沒有用好浮點數而得到錯誤結論而不自知。在科學、工程、統計上幾乎無法避免使用浮點數,所以如何趨吉避凶是每一個常用浮點數的程式員的基本知識。不過妥善運用浮點數的學問很大,學校裡頭教得不多,充其量是在數值方法這門課中學些皮毛,涵蓋面的廣度與深度相當有限,所以打算透過這篇文字提供一些使用浮點數的最基本觀念,而不做深入的理論性探討。雖然本文的程式都用Fortran 90寫作,改用其它語言的效果並無不同;另外,文中很多名詞採用直覺的涵意,而沒有很精確的定義,主要是避免太過艱澀的理論題材。正因為這個出發點,本文所需的預備知識大致上就只有最簡單的程式寫作基礎,比如單精度、倍精度、浮點數的表示方式等等,所以大多數讀者應該都可以看完這篇文字。

因為本站無法加貼圖表,為了方便起見,圖都收集到另一個網頁中,所以若有興趣繼續閱讀,請用您的瀏覽程式進入下面的網頁,不便之處請見諒。另外,這篇文字很長,希望您可以耐心看完,相信對您的工作與教學會有些幫助。
http://www.cs.mtu.edu/~shene/FORUM/Taiwan-Forum/Programming/accuracy-fig.htm
在進入正文之前要聲明一點,以下是我教計算機工程三年級數值方法講義的一部份,未來會出現在本人的著作中,所以未經同意請勿抄錄、轉載、或轉貼,敬請尊重智慧財產權。本文貼在本站(事實上目前只貼在本站)的目的,是希望在著作完成之前(也許遙遙無期)可以幫各位在不用太多學理的前提下了解一些浮點數運算的初步知識。

*** 浮點數的最根本問題 ***

使用浮點數有兩個最根本的問題:(一)輸入與儲存的值不一定精確、與(二)計算的結果會有誤差。第一點的成因不外乎以下幾項:輸入值本身就不精確、計算機儲存浮點數用的記憶體有限、不同底數之間的轉換本來就不精確、溢位、以及數學上習慣使用的運算定律在浮點數運算時不一定成立。第二,因為使用浮點數進行運算時會有上述第一點所提到的以及其它的誤差,這些誤差經過反覆而且大量運算之後很有可能會蔓延到其它所在,從而產生一發不可收拾的後果。下面各節先就第一點的成因做個介紹,稍後再討論第二點。

*** 輸入值本身不精確 ***

因為實數可能有無限位,於是就無法輸入所有位數,當然計算機得到的浮點數就不會精確;譬如說,有理數1/3、1/7、1/13等有無限位數,無理數如2的平方根、3的立方根、圓周率PI = 3.1415926……等也有無限位數。所以,我們無法完整地把這些值輸入到計算機中,從而產生誤差。圖一中是一個頂點在(n,0)、(-n,0)與 (0,sqrt(3)*n)的等邊三角形,在數學上表示這個三角形的座標不會有任何困難,但因為3的平方根有無限位數,輸入sqrt(3)*n時就不可能準確,所以輸入後儲存在計算機中的三角形會是個非常靠近等邊三角形的等腰三角形,而不是個真正的等邊三角形,於是程式中基於等邊三角形所進行的運算就很可能不精確了。

[見圖一]

     *** 請繼續看下一段 ***
作者 : shene(冼鏡光) 人氣指數超過30000點
[ 貼文 135 | 人氣 42288 | 評價 1420 | 評價/貼文 10.52 | 送出評價 0 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2007/11/23 上午 11:10:58
*** 儲存浮點數的記憶体有限 ***

計算機中儲存浮點數都是用小數加乘冪的標準型,以十進位而言就像是這樣: +/- 0. xxx…xxx 10^(+/-yyy);換言之,實數-123.456會轉換成-0.123456 x 10^3,而0.00123456會轉換成0.123456 10^(-2)。在計算機中,前面的小數部份+/-0.xxx…xxx與後面的乘冪+/-yyy會分開來儲存到記憶体中,就像是圖二那樣。

[見圖二]

在圖中,小數部份與它的符號通常是分開來的,而乘冪則另外儲存,至於乘冪部份的符號要如何儲存則取決於硬體的設計;為了避免太多技術性的討論,我們姑且不去碰IEEE浮點數算術這個標準。因為儲存小數部份與乘冪部份都只有有限個數元(bit),這就產生了兩個問題。

第一,小數部份不可能完整地儲存一個有無限位的實數(比如,根號2、1/3、圓周率等),所以會有誤差。第二,用來儲存乘冪的數元(含符號部份)比用來儲存小數部份的要少得多,如果一個浮點數的乘冪值比能夠儲存的值來得大,硬体就無法儲存這個乘冪,於是發生溢位(overflow,有時也叫做上溢位);反之,如果一個浮點數乘冪值比能夠儲存的值來得小,硬体也無法儲存這個乘冪,因而發生下溢位(underflow)。看個例子,如果硬体在儲存乘冪時的最大與最小值分別是+63與-63,於是0.1234 x 10^(68)會造成(上)溢位,0.1234 x 10^(-75)會造成下溢位。

在計算過程中如果出現上溢位與下溢位,計算機會停止作業,並且顯示floating-point exception或類似訊息,有些機型可能會明白指出上溢位、下溢位、或用0除。

*** 數系轉換並不精確 ***

我們日常用十進位數,但計算機的浮點數卻常用二進位或十六進位運算與儲存,所以寫在程式中的十進位數得轉換成二進位或十六進位,但是一個簡單的十進位數卻可能轉換成無限位的二進位或十六進位數。

圖三是把十進位的0.1轉換成二進位(左行)與十六進位(右行)的計算方式。簡單地說,我們用2或16去乘給定的十進位數,取出小數點前方的值(這是結果的一部份),再用2或16去乘剩下來的小數部份等等。在左行我們用2去乘0.1,依次得到0、0、0、1、1、0,接下來的小數部份是0.4,又回到前面出現過的內容,因此0.1(十進位)就轉換成一個二進位的循環小數0.0 0011 0011 0011 0011 ………;同理,十進位的0.1也轉換成一個十六進位的循環小數0.1999999999……。因此,簡單的十進位0.1就無法完整地在計算機中儲存;換句話說,程式中寫了0.1,但儲存到記憶体中的卻不是0.1、而是0.1的近似值,於是算出來的結果就可能不夠精確了。

[見圖三]

如果浮點數有整數與小數部份,這兩部份得分開轉換,再寫成標準型式;譬如十進位的277.31得分成277與0.31兩部份。若要把277.31轉換成十六進位,於是277轉換後得到115(十六進位),而0.31則轉換成0.4 F5C28 F5C28 F5C28 ……,一個無限位的十六進位循環小數;把兩者合併就是

115.4 F5C28 F5C28 F5C28 ……(十六進位)

寫成標準型得到

0.1154 F5C28 F6C28 F5C28 ……… x 16^3

請注意的是,上面的乘冪是用16做底數,存入計算機內就像是圖四這樣:

[見圖四]

不過,計算機不可能儲存有無限位的循環小數,於是上面結果中的後半部就會被切掉。一般而言,在單精確度(single precision)的情況下,32個數元的浮點數儲存方式可以容納六個十六進位的位數,若不計較四捨五入的規律而只用捨去的方式,計算機就只能儲存1154F5這一段,見圖五中的黃框部份:

[見圖五]

換句話說,計算機只儲存了0.1154F5 x 16^3這個數。因為0.1154F5 x 16^3等於115.4F5,把115與0.4F5轉換回十進位分別得到277與0.309814453125,所以結果是277. 309814453126,而不是原來的277.31。這表示在程式中使用277.31時,計算機內部卻用的是277. 309814453126,當然計算的結果就會有誤差。

     *** 請繼續看下一段 ***
作者 : shene(冼鏡光) 人氣指數超過30000點
[ 貼文 135 | 人氣 42288 | 評價 1420 | 評價/貼文 10.52 | 送出評價 0 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2007/11/23 上午 11:12:45
*** 溢位 ***

前面提過,在浮點數的標準型中,如果小數部份的長度超過硬体的界限,多出來的位數就會被四捨五入後存入記憶體,這就產生了誤差。但若硬体無法儲存乘冪部份,就會有溢位;如果乘冪的值大於硬体所能容許的值,結果是上溢位(overflow),如果乘冪的值(負數)小於硬体所能容許的值,結果為下溢位(underflow)。計算過程中如果發生了溢位,計算機會顯示floating-point exception的訊息、然後停止作業。

程式中的四捨五入誤差與溢位是很令人困擾的,因為前者不容易發現、一旦發生了(結果不正確)又很難追蹤;如果後者(溢位)發生了,程式會停止執行,要補救就有點遲了。所以在寫程式時,我們應該留意可能會產生很大或很小的值的所在,稍許改變程式的寫法來避免這個問題。

讓我們看個極為簡單的例子,計算a平方加b平方後再取平方根(圖六)

[見圖六]

大概沒有人不會寫這個程式,不是嗎?但您有否想到過a或b中有一者很大時所帶來的問題?若a的值很大,a的平方可能會溢位;縱使a與b不一定很大,但a平方與b平方的和卻也有可能造成溢位,雖然這個和的平方根不一定會溢位。若要降低溢位的機會,我們可以把a與b的值用圖七的手法調低:

[見圖七]

簡單地說,我們把a與b用k>0去除、再把平方根乘上k就可以得到相同的結果。那一個k值好用呢?比較理想的是k = MAX(|a|,|b|),也就是|a|與|b|中大的那一個,於是在a/k平方與b/k平方中有一者為1、另一者小於1,當然兩者都不會發生上溢位(但小的那個卻有可能產生下溢位),它們的和會小於2,開平方後的值在1與2之間,把平方根乘以k時造成溢位的可能性也低些。

下面圖八是幾何平均數的定義,所有的x值都大於0。很明顯地,若直接引用這個定義計算,當輸入值很大時出現溢位的可能性是相當大的,您可以用上述的手法避免溢位,請試試看。

[見圖八]

硬体只會對浮點數的溢位產生floating-point exception的訊息,整數的溢位通常是不理會的。舉例而言,算階乘的結果增加非常快,在一部32數元的機型上12!大致就是能夠算出沒有誤差的階乘值的極限,13!以上就會發生整數溢位使結果不正確,下面就是個好例子。程式算到12!時得到479001600,最後兩位數是0,但13!時最後兩位卻不是0,這當然是錯的,因為13! = 12! * 13,結果的最後兩位應該是0。另外,14!小於13!、19!比12!還小,不但如此,計算結果還有負值!何以致之?因為算13!時產生溢位而得到不正確的結果,於是往後的結果也都不正確。然而,計算機卻不會因為整數溢位而停止工作,重要的是,一些數值計算的工作(譬如產生整數亂數)還會用到整數溢位的能力!

   1! = 1
   2! = 2
   3! = 6
   4! = 24
   5! = 120
   6! = 720
   7! = 5040
   8! = 40320
   9! = 362880
  10! = 3628800
  11! = 39916800
  12! = 479001600
  13! = 1932053504
  14! = 1278945280
  15! = 2004310016
  16! = 2004189184
  17! = -288522240
  18! = -898433024
  19! = 109641728
  20! = -2102132736

     *** 請繼續看下一段 ***
作者 : shene(冼鏡光) 人氣指數超過30000點
[ 貼文 135 | 人氣 42288 | 評價 1420 | 評價/貼文 10.52 | 送出評價 0 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2007/11/23 上午 11:14:13
*** 數學運算定律對浮點數運算不一定成立 ***

最常見而且最常用的數學運算定律有三個:交換律(commutative law,a+b = b+a與a*b = b*a),結合律(associative law,(a+b)+c = a+(b+c)與(a*b)*c = a*(b*c)),與分配律(distributive law,(a+b)*c = a*c + b*c)。在浮點數運算時,這三者中只有交換律成立,結合律與分配律都會出問題。

圖九是兩個例子,我們使用只有兩位有效數字的運算;圖中左行是說明結合律不成立的情形。首先,0.12*0.34手算是0.0408,因為只有兩位有效數字,0.0408 = 0.408 x 10^(-1)就被四捨五入成0.41 x 10^(-1) = 0.041,因而有了四捨五入誤差;最後0.041 * 4是0.164,再次四捨五入得到0.16。另一方面,0.34*4 = 1.36,四捨五入得1.4;接著,0.12*1.4 = 0.168,再次四捨五入得到0.17。因為0.16不等於0.17,所以結合律對浮點數運算不成立!

[見圖九]

圖九右行是分配律。首先,0.12+0.99是1.11,四捨五入後是1.1,所以1.1*5 = 5.5。另一方面,0.12*5是0.6,0.99*5是4.95、四捨五入後得5.0,於是0.6+5.0為5.6。但是5.5不等於5.6,因此分配律也不成立!

事實上,結合律還可能造成溢位。考慮圖十的式子,左邊括號內的結果是1,所以等號左邊的結果是1x10^(63);但右邊括號中的結果是1x10^(126),在一些機型之下乘冪為126時多半會發生上溢位。這個例子再次告訴我們在浮點數運算時結合律不成立,因此寫作運算式時要特別小心。

[見圖十]

讓我們看個實際的計算例子。圖十一中的五道運算式在數學上是完全相同的,它們不過是把第一式用結合律與分配律改寫過而已:

[見圖十一]

我們打算寫一個程式,令R = 3.0並且從x0 = 0.5開始計算,把五道式子的結果算出來;也就是說,對每一道式子而言,我們從x0 = 0.5起算出x1,把x1代回原式算出x2,等等。為了節省空間,我們只顯示x100、x200、x300、……、與x1000的結果,這就是圖十二;圖中第一行是i的值,第二到第六行分別是上面第一到第五道式子算出的結果。仔細比較同一列上的數值,相信不難發現五道式子得來的結果有多大差異;所以,這個結果告訴我們結合律與分配律不成立時的影響有多大。

[見圖十二]

     *** 請繼續看下一段 ***

作者 : shene(冼鏡光) 人氣指數超過30000點
[ 貼文 135 | 人氣 42288 | 評價 1420 | 評價/貼文 10.52 | 送出評價 0 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2007/11/23 上午 11:15:55
*** 再談四捨五入誤差的影響 ***

前面提過,計算得來的結果會被四捨五入,當然就不精確,如果這個不精確的結果又用來計算其它的值,於是誤差可能會滾雪球般地愈來愈大,最後使程式算出完全不合情理的結果。

要注意的是,單一四捨五入所造成的誤差並不大,在最好的情況下大約是最後一位的加/減50%。譬如說,在一部有三位有效數字的機型上,如果計算結果是0.1234,就被捨去而得到0.123;若結果為0.1235,就進位得到0.124。反之,如果得到的是0.123,它可能是從0.1230、0.1231、0.1232、0.1233、或0.1234經捨去而得,也有可能是從0.1225、0.1226、0.1227、0.1228、或0.1229經進位而來。換句話說,0.123原來的值在0.1225與0.1234之間,或者說成在[0.123-0.0005,0.123+0.0005]=[0.1225,0.1235]中、這正好是最後一位的+/- 50%。

這個細微的捨入誤差很可能會導致不正確的結果。譬如說,我們希望把區間[1,2]分成三等分,並且在x為1、1 1/3、1 2/3、與2時用x的值做進一步的計算。下面是一個Fortran 90的程式,其中a、b、x、與h都是至少有15位有效數字的實數變數(見DOUBLE的定義)。程式看起來很正確、四平八穩誰都寫得出來,找不到什麼漏洞,不是嗎!?

PROGRAM Rounding
  IMPLICIT NONE
  INTEGER, PARAMETER :: DOUBLE = SELECTED_REAL_KIND(15)
  REAL(KIND=DOUBLE) :: a = 1.0_DOUBLE, b = 2.0_DOUBLE
  REAL(KIND=DOUBLE) :: x, h
  INTEGER :: n = 3

  h = (b - a)/n
  x = a
  WRITE(*,*) "x = ", x
  DO
    IF (x >= b) EXIT
    x = x + h
    WRITE(*,*) "x = ", x
  END DO
END PROGRAM Rounding

其實不然,下面是執行的結果,它多了一個輸出值2.33333…!從輸出中很明顯地可以看出,x從1起經過1.333….(小於1 1/3)、1.666….(小於1 2/3)、1.999…..(小於2),以致於多出一次循環才會得到x >= 2的結果從而離開DO,這就是捨入誤差惹來的禍。這個問題在小程式中很容易察覺,但在大而且複雜的程式中要找出多一次循環就會費工夫了。

x = 1.0
 x = 1.3333333333333332
 x = 1.6666666666666665
 x = 1.9999999999999997
 x = 2.333333333333333

在多數情況下,把IF改成下面的寫法可以避免多出一次循環的問題:

IF (x >= b – h/2) EXIT

附帶一提,在Fortran 77中DO可以用實數,所以上面的廻圈可以寫成

DO x = a, b, (b-a)/n
   WRITE(*,*) “x = “, x
END DO

Fortran 77編譯程式會從a、b與(b-a)/n的值算出循環的次數,再用整數做計數,但是在Fortran 90/95中己經建議不要使用這項功能,而且很可能在往後的新標準中消失。因此,用C/C++或Java的朋友能否由此學到些敖訓?

     *** 請繼續看下一段 ***
作者 : shene(冼鏡光) 人氣指數超過30000點
[ 貼文 135 | 人氣 42288 | 評價 1420 | 評價/貼文 10.52 | 送出評價 0 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2007/11/23 上午 11:17:43
讓我們再看一個較長而且極端的例子結束本段。如果有n > 1個值x1、x2、…、xn,它們的平均數m是這n個值的和被n去除的結果(亦即m = (x1+x2+…+xn)/n),它們的變異數(variance)定義如圖十三:

[見圖十三]

因此求變異數時要先求出平均數m,再求各個xi - m的平方和,最後用n-1去除。很多生手不喜歡這道式子,因為它要用兩個DO,第一個求各個xi的和,第二個求各個xi - m的平方和;他們也許可以在課本中找到另一道式子(圖十四),它很「漂亮」、因為一個DO就可以把xi的和與xi的平方和算出來,效率上也好一些。

[見圖十四]

不過,老手們應該會對第二式(圖十四)有所疑慮。因為xi的和也許不會造成溢位,但可能會很大,於是算和的平方時值就更大,從而在存入記憶體時會損失不少在尾部的位數造成四捨五入的誤差;這個問題也會在算xi的平方和時出現。不相信嗎?我們來看看為什麼。

我們用三個(n=3)數說明,而且每一個數都有七位、大約是32個數元所能儲存而不致於產生四捨五入的值;我們先用手算:

x1 = 9000000
x2 = 9000001
x3 = 9000002

很明顯地,我們有

x1 + x2 + x3 = 27000003
平均數 = 9000001

如果用第一式(圖十三)算變異數,我們有

變異數={(9000000–9000001)**2 + (9000001-9000001)**2 + (9000002-9000001)**2}/2 = 1

如果用第二式,那就是:

[1] x1+x2+x3 = 27000003
[2] (x1+x2+x3)**2 = 729000162000009
[3] (x1+x2+x3)**2/3 = 243000054000003
[4] x1**2 + x2**2 + x3**3 = 81000000000000 + 81000018000001 + 81000036000004 = 243000054000005
變異數 = ([4] – [3])/2 = 1

但若只有七位有效數字可用,[4]的結果會被四捨五入成七位後得到0.2430001 x 10^(15) = 243000100000000,而[3]的則可能也是同一個值,於是變異數為0,這當然是錯的。事實上,Microsoft Excel的早期版本算出來的結果就是0!

     *** 請繼續看下一段 ***
作者 : shene(冼鏡光) 人氣指數超過30000點
[ 貼文 135 | 人氣 42288 | 評價 1420 | 評價/貼文 10.52 | 送出評價 0 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2007/11/23 上午 11:19:01
下面是一個用兩道式子算變異數的Fortran 90程式,所有REAL變數都用約定的單精度32數元運算:

PROGRAM Variance
  IMPLICIT NONE
  INTEGER, PARAMETER :: SIZE = 10
  REAL, DIMENSION(1:SIZE) :: x
  REAL :: sumx, sumx2
  REAL :: mean, var1, var2
  INTEGER :: n, i
  CHARACTER(LEN=*), PARAMETER :: FMT = "(1X,A,F30.10)"

  READ(*,*) n, (x(i), i=1, n)
  WRITE(*,*) "Number of data items --> ", n
  WRITE(*,*)
  DO i = 1, n
    WRITE(*,"(1X,A,I2,A,F25.10)") "x(", i, ") = ", x(i)
  END DO

  sumx = 0.0
  sumx2 = 0.0
  DO i = 1, n
    sumx = sumx + x(i)
    sumx2 = sumx2 + x(i)**2
  END DO
  WRITE(*,*)
  WRITE(*,FMT) "Sum of x --------------> ", sumx
  WRITE(*,FMT) "(Sum of x)**2 ---------> ", sumx**2
  WRITE(*,FMT) "[(Sum of x)**2]/n -----> ", sumx**2/n
  WRITE(*,FMT) "Sum of x**2 -----------> ", sumx2

  WRITE(*,*)
  mean = sumx/n
  WRITE(*,FMT) "Mean ------------------> ", mean

  var1 = (sumx2 - sumx**2/n)/(n-1)
  WRITE(*,FMT) "One-Pass Variance -----> ", var1

  var2 = 0.0
  DO i = 1, n
    var2 = var2+ (x(i) - mean)**2
  END DO
  var2 = var2/(n-1)
  WRITE(*,FMT) "Two-Pass Variance -----> ", var2

END PROGRAM Variance

如果我們用n=3,9000000、9000001、與9000002做輸入,就會得到下面的結果;其中One-Pass是第二式(只用一個DO),而Two-Pass是第一式(用兩個DO)。程式算出來的資料的和為27000002,與實際的結果差1,但求平均數時由於四捨五入的緣故,結果還是正確的。然而因為各數的和稍有誤差,它的平方(有15位)只有前6位是完全正確的,剩下的9位並不可靠,所以除以3之後的結果也只有七位是可靠的。同理,平方和243000055693312也只有前八位可靠,所以,後者減前者的結果中16777216幾乎沒有可靠的位數(雖然它的值很大),當然算出來的變異數就十分怪異(8388608)、不可能正確。

Number of data items --> 3

 x( 1) = 9000000.0000000000
 x( 2) = 9000001.0000000000
 x( 3) = 9000002.0000000000

 Sum of x --------------> 27000002.0000000000
 (Sum of x)**2 ---------> 729000099971072.0000000000
 [(Sum of x)**2]/n -----> 243000038916096.0000000000
 Sum of x**2 -----------> 243000055693312.0000000000

 Mean ------------------> 9000001.0000000000
 One-Pass Variance -----> 8388608.0000000000
 Two-Pass Variance -----> 1.0000000000

     *** 請繼續看下一段 ***
作者 : shene(冼鏡光) 人氣指數超過30000點
[ 貼文 135 | 人氣 42288 | 評價 1420 | 評價/貼文 10.52 | 送出評價 0 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2007/11/23 上午 11:20:15
我們或許會想到用倍精度來增加位數、從而解決這個問題,不過我們仍然可以找到三個足夠大的數使倍精度的程式發生同樣的困擾。所以,根本的做法是採用一個好的算法,而不是肓目地提昇精確度;以這個算變異數的題目為例,就是用兩個DO的寫法。

上面也提過算平均數時得箕各資料的和,這個和也許會很大、使計算機無法容納所有位數,從而產生四捨五入誤差。但平均數會在最大與最小的值之間,所以在能夠相當精確地儲存最大與最小值的前提下,也應該可以很精確地儲存平均數。事實上,求平均數與變異數都有所謂的「更新」公式,它把目前算出的平均數(或變異數)與下一個資料結合而算出新的平均數(或變異數),於是就避免了求各項和時產生很大的值,從而降低誤差。

*** 失去精確位數 ***

另一個讓人頭痛的浮點數問題是使用減法時可能會失去精確位數。下面的Fortran程式中x與y的值都有16位,在一部有15位精確度的機型中最多只有最後兩位不很精確,它們的差(手算)是0.000000000004111,於是兩個原來有十五位精確位數的值相減後只留下四位,因此精確位數最多也不過四位;換言之,兩個值幾乎相等的浮點數經過減法運算後會失去很多精確位數。

PROGRAM Cancellation
  IMPLICIT NONE
  INTEGER, PARAMETER :: DOUBLE = SELECTED_REAL_KIND(15)
  REAL(KIND=DOUBLE) :: x = 3.141592653589793_DOUBLE
  REAL(KIND=DOUBLE) :: y = 3.141592653585682_DOUBLE
  REAL(KIND=DOUBLE) :: z
  CHARACTER(LEN=*), PARAMETER :: FMT = "(1X, A, E23.17)"

  z = x - y
  WRITE(*,*) "x = ", x
  WRITE(*,*) "y = ", y
  WRITE(*,*) "x = y = ", z
END PROGRAM Cancellation

下面是程式的輸出,兩者的差仍然有15位,但這15位中只有最前面三位411是可信的,雖然從第5位的9經過四捨五入後會得到4111的結果,但是在程式執行時我們不一定能夠了解在何處四捨五入,所以程式的計算結果雖然有足夠的15位(倍精度),但可靠的卻只有三到四位;這個失去精確位數的成因,就是兩個幾乎相等的值經過減法運算的結果。另外,在前面討論計算變異數時,我們也談到過失去精確位數的現象,得來的結果全然不可信。

x = 3.141592653589793
 y = 3.141592653585682
 x - y = 4.11093381558203E-12

把兩個幾乎相等的值相減常常不是程式的本意,而是從其它沒有處理好的計算所衍生出來的結果,正因為如此,失去精確位數的成因往往不容易發現。下面就是一個簡單的例子,求一元二次方程式ax^2 + bx + c = 0的根(見圖十五):

[見圖十五]

程式十分容易,相信在學初級程式設計時大概都寫過,下面是個Fortran 90的例子,所有實數變數都用單精度(約7位精確位數):

PROGRAM Degree2_Eqn
  IMPLICIT NONE
  REAL :: a, b, c
  REAL :: d, r1, r2
  CHARACTER(LEN=*), PARAMETER :: FMT = "(1X,A,E15.7)"

  READ(*,*) a, b, c
  WRITE(*,FMT) "a = ", a
  WRITE(*,FMT) "b = ", b
  WRITE(*,FMT) "c = ", c

  d = SQRT(b*b - 4.0*a*c)
  r1 = (-b + d)/(2.0*a)
  r2 = (-b - d)/(2.0*a)
  WRITE(*,*)
  WRITE(*,FMT) "d = ", d
  WRITE(*,FMT) "Root 1 = ", r1
  WRITE(*,FMT) "Root 2 = ", r2
END PROGRAM Degree2_Eqn

     *** 請繼續看下一段 ***
作者 : shene(冼鏡光) 人氣指數超過30000點
[ 貼文 135 | 人氣 42288 | 評價 1420 | 評價/貼文 10.52 | 送出評價 0 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2007/11/23 上午 11:22:34
這個程式正確嗎?是的。這個程式寫得好嗎?以初級程式設計而言,很好;就專業角度而言,不好!這個不好並不是因為沒有考慮到a = 0與b^2 – 4ac < 0等等的狀況,而是它很容易發生失去精確度的問題(因為分子中有減法)。避免b^2-4ac中的減法複雜些,所以姑且不論,剩下來在根號前面的+/-就是問題所在,我們看看為什麼。

如果b的值很大而a與c卻很小,於是b^2就更大,當然b^2 – 4ac的結果差不多就是b^2,所以b^2-4ac的平方根就與b非常靠近,經過減法後分子很可能是0。譬如說,若a = c = 1而b = 20000,於是b^2 = 400000000,b^2 – 4ac = 399999996 = 0.399999996 x 10^(9)。如果用單精度(7位)計算,這個數會被四捨五入成0.4000000x10^(9),因此b^2-4ac的平方根為20000,於是一個根是(-20000+20000)/2 = 0,另一個根則是(-20000-20000)/2 = -20000!這當然不可能是正確的結果,因為ax^2 + bx + c = 0兩個根的和是-b/a、兩個根的積是c/a;然而輸入的方程式為x^2 + 20000x + 1 = 0,不可能有為0的根!下面就是程式的執行結果。

a = 0.1000000E+01
 b = 0.2000000E+05
 c = 0.1000000E+01

 d = 0.2000000E+05
 Root 1 = 0.0000000E+00
 Root 2 = -0.2000000E+05

如何去掉分子的減號呢?請記位一點,兩個根的積是c/a,所以從根的式子算出一者,令為r1(假設不是0,如果是0就容易了),另一個根就是(c/a)/r1。我們可以這樣想:如果b < 0,-b就是正數,所以-b+SQRT(b^2-4ac)是兩個正數的和;反之,若b > 0,則-b小於0,-b-SQRT(b^2-4ac)是兩個負數的和。這樣,減法運算就沒有了。下面的程式片段是根據這個原則稍許修改而成:

  d = SQRT(b*b - 4.0*a*c)
  IF (b > 0) THEN
    r1 = -b - d
  ELSE
    r1 = -b + d
  END IF
  r1 = r1/(2*a)
  r2 = (c/a)/r1

以下是計算的結果,兩個根是-20000與-0.00005。但是,這兩個根的和是-20000.00005,而不是-20000,結果仍然不十分準確;不過就浮點數運算而言,在單精度(7位)運算下,20000.00005會被四捨五入成20000,所以結果是正確的!雖然有稍許誤差,這總是要比完全錯誤的結果好太多了。

a = 0.1000000E+01
 b = 0.2000000E+05
 c = 0.1000000E+01

 d = 0.2000000E+05
Root 1 = -0.2000000E+05
 Root 2 = -0.5000000E-04

去掉減法不是件容易的工作,而且還不一定做得到,不過有一個技巧是常常用到的,這就是圖十六的式子。如果式子中出現a-b,我們可以乘上(a+b)/(a+b)得到(a^2-b^2)/(a+b),若a^2-b^2的部份可以化簡到沒有減號,那就大功告成了;當然,這個技巧不是萬靈丹,如果不成功就得用其它技巧。要注意的是,只有當a與b的值幾乎相等而會造成損失精確位數時,才需要做這道手續;如果已知a與b的值相差不小而不致於發生損失精確位數時,做這樣的轉換就沒多大意義了。

[見圖十六]

     *** 請繼續看下一段 ***
作者 : shene(冼鏡光) 人氣指數超過30000點
[ 貼文 135 | 人氣 42288 | 評價 1420 | 評價/貼文 10.52 | 送出評價 0 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2007/11/23 上午 11:24:07
讓我們看看如何算圖十七的式子,這道式子只有在x很大時才會出問題。當x很大時,因為四捨五入的原因,x+1的值可能非常靠近x,於是x+1的平方根就與x的平方根幾乎相等甚或相等,有造成用0除的可能性;縱使不是這兩種情況,因為兩個幾乎相等的值相減後會損失精確位數,得來的結果也不會十分準確。

[見圖十七]

如果我們用圖十八的手法,圖十七就可以酗あ肝+1的平方根與x的平方根的和,當然減法就消失了。不過要注意的是,不是每一道式都可以用這個手法處理,有時還得用其它數學技巧。

[見圖十八]

我們不妨寫個用單精度(7位)的程式來証實上面的分析,這就是圖十九。這張表的第一行是十個從小到大的x值,它們至少都有七位。第二行是x+1的平方根減x的平方根,從表中可以看出,當x為7777777、9999999、與123456789時兩者的差為零,當然算倒數時就會發生用0除的floating-point exception;另外,因為失去了精確位數,從3333333起算出來的差如果不是零就是同一值,當然都是不正確的。第三行是第二行的倒數,正是圖十七中式子的值;由於第二行的值不準確,第三行的也不準確。第四行是採用圖十八的算法,不妨用手上的計算機驗算,看看是否準確得多!?最後,因為x很大時x+1與x很靠近,當然它們的平方根也很靠近,於是第四行的結果(在x很大時)就差不多是x的平方根的兩倍,這是第五行的結果,從x = 4444444起兩行的結果就幾乎一樣了。從這張表我們可以看出失去精確位數的影響、以及轉換式子去掉減號的重要性。

[見圖十九]

下面是幾道練習題,有興趣的朋友不妨試一試:

(1) (1-cos(x))/sin(x), x非常靠近0
(2) (1-cos(x))/x^2, x非常靠近0(提示:考慮半角公式)
(3) sin(x)-cos(x), x非常靠近PI/4(提示:考慮半角公式)

*** 幾個「無心」但後果嚴重的浮點數過失 ***

在計算機的歷史中有不少因為浮點數的錯誤而產生嚴重後果的例子,以下舉幾個供大家參考。

第一,為了避免被敵軍偵測到位置,美國愛國者(Patriot)反飛彈系統在設計時就打算在每一個點只停留並且運轉若干小時後就移往它處。來襲飛彈的速度是用一個浮點數表示,但愛國者飛彈內部的時鐘卻是一個整數,1相當於1/10秒;雖然這個時鐘的解析度不高(許多計算機中1代表1/100秒),但用整數值計時是常見的。這個整數時間會乘上0.1成為一個24個數元的二進位浮點數後再使用,但是0.1的十進位在轉換成二進位時是個循環小數(我們在前面提過),所以轉換後的誤差大約是9.5 x 10^(-8)。

來襲飛彈位置的誤差與來襲飛彈的速度和系統運轉時間的乘積成正比。如果一個愛國者飛彈系統運轉100小時,來襲飛彈的速度是每秒1676公尺,位置的誤差就有573公尺。在第一次波斯灣戰爭時,1991年2月15日伊拉克向在沙烏地阿拉伯達蘭市(Dhahran)的美軍射出一枚飛毛腿(Scud)飛彈,但由於上述的計算誤差使愛國者飛彈沒有成功攔截到來襲飛彈,造成28名美軍陣亡。

第二,溫哥華股市在每筆交易後都會更新它的股票指數,這個指數有三位小數,在更新之後把第四位之後的位數捨去而留下三位。經過22個月後,股票指數從起初的1000跌到524.881,但是正確算出來的指數卻是1098.881(上漲)。這是什麼時代,還會發生這種簡單的錯誤?很抱歉,這是近代的事,1982年!

     *** 請繼續看下一段 ***
作者 : shene(冼鏡光) 人氣指數超過30000點
[ 貼文 135 | 人氣 42288 | 評價 1420 | 評價/貼文 10.52 | 送出評價 0 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2007/11/23 上午 11:25:24
第三,1996年歐州太空機構(European Space Agency)在法屬幾內亞(French Guiana)的Kourou地方發射Ariane 5無人火箭,40秒後火箭失去控制必須引爆摧毀。事後追查原因,居然是一位程式員把一個代表某種控制訊號的64個數元的浮點數存入一個有16個數元的帶符號整數(最大值為32767),所以訊號全都錯亂,使火箭在空中失去控制。專業程式員會犯這種錯,當初是如何測試系統的,是不是覺得很奇怪!?

第四,1997年9月,美國海軍USS Yorktown號的水兵在操作時不小心輸入了0,於是造成用0除的問題;不但如此,錯誤的結果不斷蔓延,終究把艦上的推進系統關掉(停俥),使軍艦在海面上靜止2小時45分鐘,還好這不是在戰時。想想一個程式不撿查除數是否為0、不檢查輸入是否正確,這是否有點不可思議!?

話又說回來,我們在寫程式時是否已經儘力避免這些細微但明顯的錯誤?有沒有因為類似的錯誤而使公司災情慘重,或是使公司甚或政府做出錯誤的決策?我常在想一個問題,計算機科系因為開了很多本科的課,而不再講授數值方法(因為本科的學生用不著,真的嗎?我不知道),甚至於連本文的基本觀念都不提,所以本科系學生在浮點數運算的基本知識上很可能是一片空白。非本科的學生大概也學不到這些內容,要用的話就找一套程式庫(IMSL、NAG等)或Matlab等,這當然解決了難的部份,但是在用這些程式庫之前,自己總得先整理資料,如果在過程中出現誤差或錯誤,垃圾進垃圾出就會發生。然而日常程式寫作中總是會用到浮點數,為了避免產生與浮點數有關的錯誤,我們還是得要了解運用浮點數的基本原則的,不論您是本科還是非本科的學生。

*** 綜合討論 ***

前面把幾個使用浮點數時容易出錯的地方分開來用例子做了說明,不過浮點數的問題並不單獨存在,而是與在出問題處所使用的資料有關;換言之,在很多情況下,出問題之前所算出的值可能才是關鍵所在。就用減法為例,出問題所在的減法可能沒有問題,真正問題也許是計算該減法兩個運算元的值的所在。另外,如果減法運算元的值是準確的(譬如輸入值),這個減法也非做不可,所以一些使用大量減法的程式也不會出什麼大差錯(因為它們用原始的輸入值)。有時候,不論如何轉換也無法去掉減法,甚至於去掉了減法後得來的結果仍然不精確,成因很可能是所處理題目本身就很不容易算得精確(ill-conditioned),因此更換演算法才是正途。還有,有些減法是無害的;譬如說,若a與b幾乎相等,但a – b的值卻比c小很多,於是(a-b)+c中的減法就不會有什麼影響(請問為什麼?)。

四捨五入的誤差又如何呢?幸運的是,在大多數情況下捨去與進位的誤差會相互抵消,而且由捨入誤差造成的困擾很可能只源自若干點、而不一定是由大量反覆運算而得,於是找對了源頭就有辦法找出對策。圖二十是一個十分簡單的例子,您也許在微積分中學到過;Euler証明了這個極限值是e = 2.71828.....,我們打算寫一個程式讓n愈來愈大,從而算出極限值。

[見圖二十]

程式十分容易寫如下,我們用n = 10, 100, 1000, 10000, … 等nn個數值代入上式看看是否會得到近似2.71828…的結果。請注意的是,這個程式片段中的四捨五入只侷限在算E的式子而不會蔓延。
  n = 10
  EE = exp(1.0)
  DO i = 1, nn
    E = (1.0 + 1.0/n)**n
    write(*,*) n, E, abs(E-EE)
    n = n*10
  END DO

     *** 請繼續看下一段 ***
作者 : shene(冼鏡光) 人氣指數超過30000點
[ 貼文 135 | 人氣 42288 | 評價 1420 | 評價/貼文 10.52 | 送出評價 0 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2007/11/23 上午 11:26:31
下面是計算結果,當n到達10000時的近似值為2.718597、與真正e的值差3.1518936E-4,但自此之後算出來的值與真正的e值差距愈來愈大,最後結果居然是1,很明顯的錯誤!問題何在?正是在不會蔓延的算E時的捨入誤差。前面提過,0.1的十進位轉換成二進位或十六進位時會是循環小數,當然1/10^n也是如此,於是存入記憶體就會有捨入誤差。不過根本問題並不在此,而是在加上1的結果!如果計算機有七位精確位數,1/10^7 = 0.0000001,加上1之後為1.0000001,寫成標準型是0.10000001 x 10^1,四捨五入到七位後得到0.1 x 10^1,這就是最終結果為1的原因。所以,捨入誤差並不一定得蔓延才會產生問題,關鍵在找出產生問題的所在!以這個程式而言,解決之道就是改寫這道式子來降低捨入誤差(這是個頗有挑戰性的問題,數學好的朋友不妨一試)。

10 2.593743 0.12453866
     100 2.7048113 0.013470411
     1000 2.7170507 1.2309551E-3
     10000 2.718597 3.1518936E-4
     100000 2.7219622 3.6804676E-3
    1000000 2.5952267 0.12305498
   10000000 3.2939677 0.575686
  100000000 1.0 1.7182817
1000000000 1.0 1.7182817

另一個看起來相當「明顯」的做法是改用倍精度來增加精確位數,這是可行的;事實上,當n = 100000000時,倍精度運算得到2.7182817983473577,與真正e的誤差是3.011168780986395E-8!

遺憾的是,提高精確度並不是萬靈丹,無法解決所有捨入誤差所帶來的問題。您不妨找個計算機、輸入一個正數,再按開方鍵若干次得到一個數,接著按同樣多次的平方鍵,看看會有什麼結果。一般而言,結果會與輸入值相差不大,不過若按開方鍵次數足夠多,您會發現結果會愈來愈靠近1,於是再平方回來時與原本的輸入就有很大的差距;當然,一旦開方的結果變成1,不論如何平方都不可能得到原來的值。這個現象在任何計算機、使用任何精確度都成立,所差的就是何時變成1而已。

我們也可以寫一個程式驗証這個現象;下面是程式的(部份)輸出,這個程式求2的平方根若干次,再把結果求平方相同次數。首先,我們開方十次再平方十次,於是第十次開方得到1.0006771088:

    0 2.0000000000
     ………
    5 1.0218970776
     ………
   10 1.0006771088

再平方十次,結果是1.9999581575,誤差是0.00004左右,還算好。

    0 1.0006771088
     ………
    5 1.0218964815
     ………
   10 1.9999581575

如果把開方次數增加到25次,下面是結果,到第23次結果就是1,當然平方回來就不可能得到2!

    0 2.0000000000
     ………
   10 1.0006771088
     ………
   15 1.0000211000
     ………
   20 1.0000005960
   21 1.0000002384
   22 1.0000001192
   23 1.0000000000
   24 1.0000000000
   25 1.0000000000

改用倍精度又如何?因為單精度(七位)差不多在開方23到25次就得到1,有十五位的倍精度大約用50(或多幾)次開方也會得到1。所以,就這個問題而言,增加精確度只是延後問題出現的時間而已,當然,如果在問題發生之前就己經得到可用的結果,那就提到精確度吧!

     *** 請繼續看下一段 ***
作者 : shene(冼鏡光) 人氣指數超過30000點
[ 貼文 135 | 人氣 42288 | 評價 1420 | 評價/貼文 10.52 | 送出評價 0 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2007/11/23 上午 11:27:26
*** 結論 ***

前面用不少例子說明使用浮點數的最基本課題,但我們沒有做嚴謹的定義、也沒有用嚴格的論証,只用到最基本寫程式的知識,這樣做的用意是希望一般程式員(不論是入門或資深)甚至於學生都看得懂。以下是幾點最粗淺的建議:

第一, 儘可能避免使用兩個值幾乎相同的運算元的減法,除非這兩個運算元的值是精確的、或是減法的結果對往後的運算影響不大。
第二, 留神溢位或近似溢位的狀況。一般而言,如果算出來的中間結果比最終結果(與輸入資料)大很多或小很多,就是可能會產生溢位的徵候,在這個情況下不妨改用其它式子或把值做比例放大或縮小,以避免溢位。
第三, 留神捨入誤差,特別是把兩個相差極大的值相加或相減時,小的那個值可能完全沒有貢獻,從而得到不正確的結果。
第四, 不要隨便抄課本的演算法或程式出來用。課本中的演算法與程式的重點是在解釋觀念,除非作者很明白地指出該演算法或程式已經考量到數值計算的穩定度(stable)等課題,或者是該演算法或程式沒有穩定度的問題;如果不是如此,當程式的輸入比較「怪異」時,該演算法或程式的輸出很可能不正確。

最後,希望這篇長文對您在未來使用浮點數上頭有點助益。

     *** 全文完 ***
作者 : chiuinan2(青衫)討論區板主 Visual C++ .NET卓越專家VC++一代宗師Visual Basic優秀好手資訊類作業求救卓越專家一般曠世奇才程式設計甘苦談優秀好手C++ Builder優秀好手上班族的哈拉園地優秀好手C++頂尖高手Assembly優秀好手貼文超過3000則人氣指數超過150000點
[ 貼文 3726 | 人氣 170106 | 評價 34340 | 評價/貼文 9.22 | 送出評價 125 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2007/11/24 上午 09:36:35
這是近期內看過最棒的文章了, 值得讚賞.
作者 : shene(冼鏡光) 人氣指數超過30000點
[ 貼文 135 | 人氣 42288 | 評價 1420 | 評價/貼文 10.52 | 送出評價 0 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2007/12/21 下午 03:47:09
一個月不到這篇文字就沈到底,我再把它浮上來。

如果您常用浮點(實)數,建議您有耐心地把它看完,免得在程式中犯了不自知的致命錯誤。用浮點(實)數與用整數不一樣,不小心犯的小錯是很可能引出大亂子的,文中就有幾個這樣的例子。
作者 : knicks_a(knicks_a)
[ 貼文 3 | 人氣 1 | 評價 0 | 評價/貼文 0 | 送出評價 0 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2007/12/31 上午 11:24:38
好文*1,文中提到一些小細節,我以前都沒注意到這些...看完之後,又多學到了一些~Thanks a lot!
作者 : simenchen(西門慶)
[ 貼文 39 | 人氣 344 | 評價 270 | 評價/貼文 6.92 | 送出評價 0 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2008/1/5 下午 01:20:20
我以前一直以為提高精度就搞定了
原來只是把發生問題的時機往後延
並沒有真正了解問題+正確解決問題
感謝大大破除了我觀念上的錯誤
否則發生問題的時候
我可能會換一顆CPU 噗LOL
作者 : sjgau02(john) 人氣指數超過10000點
[ 貼文 122 | 人氣 11166 | 評價 270 | 評價/貼文 2.21 | 送出評價 3 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2008/1/5 下午 05:50:04
整數的問題,也是很多。

in C,

char ch1;// -128 .. +127

short no;// -32768 .. +32767

long max1; // -2147483648 .. +2147483647

no= 20000;
no= no + no;// 會變成 負的

很多人都沒有注意到 這個現象

作者 : xwlin(XWLin) 人氣指數超過10000點
[ 貼文 147 | 人氣 20298 | 評價 430 | 評價/貼文 2.93 | 送出評價 3 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2008/1/5 下午 09:49:52
感謝沈老師的文章。
只是想請教一下,如果真的需要很精確的數值運算時,那該如何讓精確度增加呢?
因為就算是使用倍精準的浮點數,也是有其精確度的上限。
作者 : shene(冼鏡光) 人氣指數超過30000點
[ 貼文 135 | 人氣 42288 | 評價 1420 | 評價/貼文 10.52 | 送出評價 0 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2008/5/13 下午 02:46:00
本文已經重新整理過,附上圖之後貼在我的blog上:http://blog.dcview.com.tw/article.php?a=BjhUMQdvATA%3D 希望這個版本讀起來容易些。
 板主 : Jammy , simula
 > 一般討論區 - 討論區
 - 最近熱門問答精華集
 - 全部歷史問答精華集
 - 一般討論區 - 知識庫
  ■ 全站最新Post列表
  ■ 我的文章收藏
  ■ 我最愛的作者
  ■ 全站文章收藏排行榜
  ■ 全站最愛作者排行榜
  ■  月熱門主題
  ■  季熱門主題
  ■  熱門主題Top 20
  ■  本區Post排行榜
  ■  本區評價排行榜
  ■  全站專家名人榜
  ■  全站Post排行榜
  ■  全站評價排行榜
  ■  全站人氣排行榜
 請輸入關鍵字 
  開始搜尋
 
Top 10
評價排行
一般討論區
1 青衫 5360 
2 HKLN.net 1370 
3 冼鏡光 650 
4 simula 610 
5 joe 560 
6 DEMO999 520 
7 小朱 490 
8 jonay 480 
9 BlueTulip 460 
10 Jammy 370 
一般討論區
  專家等級 評價  
  一代宗師 10000  
  曠世奇才 5000  
  頂尖高手 3000  
  卓越專家 1500  
  優秀好手 750  
Microsoft Internet Explorer 6.0. Screen 1024x768 pixel. High Color (16 bit).
2000-2014 程式設計俱樂部 http://www.programmer-club.com.tw/
0.140625