冼鏡光
May 12, 2008上線
December 28, 2024搬家到此
不知道各位在程式中使用浮點(或實)數的機會有多大,也不知道各位的程式有否因為浮點數問題而不能產生正確結果的困擾,
也不知道是否有程式因為沒有用好浮點數而得到錯誤結論而不自知。
在科學、工程、統計上幾乎無法避免使用浮點數,所以如何趨吉避凶是每一個常用浮點數的程式員的基本知識。
不過妥善運用浮點數的學問很大,學校裡頭教得不多,充其量是在數值方法這門課中學些皮毛,涵蓋面的廣度與深度相當有限,
所以打算透過這篇文字提供一些使用浮點數的最基本觀念,而不做深入的理論性探討。
雖然本文的程式都用Fortran 90寫作,改用其它語言的效果並無不同;
另外,文中很多名詞採用直覺的涵意,而沒有很精確的定義,主要是避免太過艱澀的理論題材。
正因為這個出發點,本文所需的預備知識大致上就只有最簡單的程式寫作基礎,比如單精度、倍精度、浮點數的表示方式等等,
所以大多數讀者應該都可以看完這篇文字。
這篇文字很長,希望您可以耐心看完,相信對您的工作與教學會有些幫助。
1. 浮點數的最根本問題
使用浮點數有兩個最根本的問題:
(1)輸入與儲存的值不一定精確、與(2)計算的結果會有誤差。
第一點的成因不外乎以下幾項:輸入值本身就不精確、計算機儲存浮點數用的記憶體有限、
不同底數之間的轉換本來就不精確、溢位、以及數學上習慣使用的運算定律在浮點數運算時不一定成立。
第二,因為使用浮點數進行運算時會有上述第一點所提到的以及其它的誤差,
這些誤差經過反覆而且大量運算之後很有可能會蔓延到其它所在,從而產生一發不可收拾的後果。
下面各節先就第一點的成因做個介紹,稍後再討論第二點。
1.1 輸入值本身不精確
因為實數可能有無限位,於是就無法輸入所有位數,當然計算機得到的浮點數就不會精確;
譬如說,有理數1/3、1/7、1/13等有無限位數,無理數如2的平方根、3的立方根、
圓周率&\pi; = 3.1415926……等也有無限位數。
所以,我們無法完整地把這些值輸入到計算機中,從而產生誤差。
圖一中是一個頂點在(n,0)、(-n,0)與 (0,√3n)的等邊三角形,
數學中表示這個三角形的座標不會有任何困難,但因為3的平方根有無限位數,輸入√3n時就不可能準確,
所以輸入後儲存在計算機中的三角形會是個非常靠近等邊三角形的等腰三角形,而不是個真正的等邊三角形,
於是程式中基於等邊三角形所進行的運算就很可能不精確了。
在圖中,小數部份與它的符號通常是分開來的,而乘冪則另外儲存,至於乘冪部份的符號要如何儲存則取決於硬體的設計; 為了避免太多技術性的討論,我們姑且不去碰IEEE浮點數算術這個標準。 因為儲存小數部份與乘冪部份都只有有限個數元(bit),這就產生了兩個問題。
第一,小數部份不可能完整地儲存一個有無限位的實數(比如,根號2、1/3、圓周率等), 所以會有誤差。 第二,用來儲存乘冪的數元(含符號部份)比用來儲存小數部份的要少得多, 如果一個浮點數的乘冪值比能夠儲存的值來得大,硬體就無法儲存這個乘冪,於是發生溢位 (overflow,有時也叫做上溢位); 反之,如果一個浮點數乘冪值比能夠儲存的值來得小,硬體也無法儲存這個乘冪,因而發生 下溢位(underflow)。 看個例子,如果硬體在儲存乘冪時的最大與最小值分別是+63與-63,於是0.1234 × 1068會造成(上)溢位, 0.1234 × 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 ……,一個無限位的十六進位循環小數;把兩者合併就是
寫成標準型得到
請注意的是,上面的乘冪是用16做底數,存入計算機內就像是圖四這樣:
不過,計算機不可能儲存有無限位的循環小數,於是上面結果中的後半部就會被切掉。 一般而言,在單精確度(single precision)的情況下,32個數元的浮點數儲存方式可以容納六個十六進位的位數, 若不計較四捨五入的規律而只用捨去的方式,計算機就只能儲存 1154F5這一段,見圖五中的黃框部份:
換句話說,計算機只儲存了0.1154F5 × 163這個數。 因為0.1154F5 × 163等於115.4F5, 把115與0.4F5轉換回十進位分別得到277與0.309814453125,所以結果是277. 309814453126,而不是原來的277.31。 這表示在程式中使用277.31時,計算機內部卻用的是277.309814453126,當然計算的結果就會有誤差。
程式中的四捨五入誤差與溢位是很令人困擾的,因為前者不容易發現、一旦發生了(結果不正確)又很難追蹤; 如果後者(溢位)發生了,程式會停止執行,要補救就有點遲了。 所以在寫程式時,我們應該留意可能會產生很大或很小的值的所在,稍許改變程式的寫法來避免這個問題。
讓我們看個極為簡單的例子,計算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
圖九是兩個例子,我們使用只有兩位有效數字的運算;圖中左行是說明結合律不成立的情形。 首先,0.12×0.34手算是0.0408,因為只有兩位有效數字,0.0408 = 0.408 × 10-1 就被四捨五入成0.41 ×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,所以等號左邊的結果是1×1063; 但右邊括號中的結果是1×10126,在一些機型之下乘冪為126時多半會發生上溢位。 這個例子再次告訴我們在浮點數運算時結合律不成立,因此寫作運算式時要特別小心。
讓我們看個實際的計算例子。 圖十一中的五道運算式在數學上是完全相同的,它們不過是把第一式用結合律與分配律改寫過而已:
我們打算寫一個程式,令R = 3.0並且從x0 = 0.5開始計算,把五道式子的結果算出來; 也就是說,對每一道式子而言,我們從x0 = 0.5起算出x1, 把x1代回原式算出x2,等等。 為了節省空間,我們只顯示x100、x200、x300、……、 與x1000的結果,這就是圖十二; 圖中第一行是i的值,第二到第六行分別是上面第一到第五道式子算出的結果。 仔細比較同一列上的數值,相信不難發現五道式子得來的結果有多大差異;所以,這個結果告訴我們結合律與分配律不成立時的影響有多大。
要注意的是,單一四捨五入所造成的誤差並不大,在最好的情況下大約是最後一位的加/減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]中、這正好是最後一位的&pmn;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的朋友能否由此學到些教訓?
讓我們再看一個較長而且極端的例子結束本段。 如果有n > 1個值x1、x2、…、xn, 它們的平均數m是這n個值的和被n去除的結果 (亦即m = (x1+x2 +…+xn)/n),它們的變異數(variance)定義如圖十三:
因此算變異數時要先求出平均數m, 再求各個xi - m的平方和,最後用n-1去除。 很多生手不喜歡這道式子,因為它要用兩個DO, 第一個求各個xi的和,第二個求各個xi的平方和; 他們也許可以在課本中找到另一道式子(圖十四),它很「漂亮」、 因為一個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] x12 + x22 + x32 = 81000000000000 + 81000018000001 + 81000036000004 = 243000054000005 變異數 = ([4] – [3])/2 = 1
下面是一個用兩道式子算變異數的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
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
上面也提過算平均數時得箕各資料的和,這個和也許會很大、使計算機無法容納所有位數,從而產生四捨五入誤差。 但平均數會在最大與最小的值之間,所以在能夠相當精確地儲存最大與最小值的前提下,也應該可以很精確地儲存平均數。 事實上,求平均數與變異數都有所謂的「更新」公式,它把目前算出的平均數(或變異數) 與下一個資料結合而算出新的平均數(或變異數),於是就避免了求各項和時產生很大的值,從而降低誤差。
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
x = 3.141592653589793 y = 3.141592653585682 x - y = 4.11093381558203E-12
程式十分容易,相信在學初級程式設計時大概都寫過,下面是個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
如果b的值很大而a與c都很小,於是b2就更大, 當然b2 – 4ac的結果差不多就是b2, 所以b2 – 4ac的平方根就與b非常靠近,經過減法後分子很可能是0。 譬如說,若a = c = 1而b = 20000,於是b2 = 400000000, b2 – 4ac = 399999996 = 0.399999996 ´× 109。 如果用單精度(7位)計算,這個數會被四捨五入成0.4000000 × 109, 因此b2 – 4ac的平方根為20000, 於是一個根是(-20000+20000)/2 = 0,另一個根則是(-20000-20000)/2 = -20000! 這當然不可能是正確的結果,因為ax2 + bx + c = 0 兩個根的和是-b/a、兩個根的積是c/a; 然而輸入的方程式為x2 + 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
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
a = 0.1000000E+01 b = 0.2000000E+05 c = 0.1000000E+01 d = 0.2000000E+05 R oot 1 = -0.2000000E+05 Root 2 = -0.5000000E-04
讓我們看看如何算圖十七的式子,這道式子只有在x很大時才會出問題。 當x很大時,因為四捨五入的原因,x+1的值可能非常靠近x, 於是x+1的平方根就與x的平方根幾乎相等甚或相等,有造成用0除的可能性。 縱使不是這兩種情況,因為兩個幾乎相等的值相減後會損失精確位數,得來的結果也不會十分準確。
如果我們用圖十八的手法,圖十七就可以轉化成x+1的平方根與x的平方根的和,當然減法就消失了。 不過要注意的是,不是每一道式都可以用這個手法處理,有時還得用其它技巧。
我們不妨寫個用單精度(7位)的程式來証實上面的分析,這就是圖十九。 這張表的第一行是十個從小到大的x值,它們至少都有七位。 第二行是x+1的平方根減x的平方根,從表中可以看出,當x為7777777、9999999、與123456789時兩者的差為零, 當然算倒數時就會發生用0除的floating-point exception; 另外,因為失去了精確位數,從3333333起算出來的差如果不是零就是同一值,當然都不正確。 第三行是第二行的倒數,正是圖十七中式子的值;由於第二行的值不準確,第三行的也不準確。 第四行是採用圖十八的算法,不妨用手上的計算機驗算,看看是否準確得多!? 最後,因為x很大時x+1與x很靠近,當然它們的平方根也很靠近, 於是第四行的結果(在x很大時)就差不多是x的平方根的兩倍, 這是第五行的結果,從x = 4444444起兩行的結果就幾乎一樣了。 從這張表我們可以看出失去精確位數的影響、以及轉換式子去掉減號的重要性。
下面是幾道練習題,有興趣的朋友不妨試一試:
來襲飛彈位置的誤差與來襲飛彈的速度和系統運轉時間的乘積成正比。 如果一個愛國者飛彈系統運轉100小時,來襲飛彈的速度是每秒1676公尺,位置的誤差就有573公尺。 在第一次波斯灣戰爭時,1991年2月15日伊拉克向在沙烏地阿拉伯達蘭市(Dhahran) 的美軍射出一枚飛毛腿(Scud)飛彈,但由於上述的計算誤差使愛國者飛彈沒有成功攔截到來襲飛彈, 造成28名美軍陣亡。
四捨五入的誤差又如何呢?幸運的是,在大多數情況下捨去與進位的誤差會相互抵消, 而且由捨入誤差造成的困擾很可能只源自若干點、而不一定是由大量反覆運算而得,於是找對了源頭就有辦法找出對策。 圖二十是一個十分簡單的例子,您也許在微積分中學到過; Euler証明了這個極限值是e = 2.71828.....,我們打算寫一個程式讓n愈來愈大,從而算出極限值。
程式十分容易寫如下,我們用n = 10, 100, 1000, 10000, … 等n個數值代入上式看看是否會得到近似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
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
遺憾的是,提高精確度並不是萬靈丹,無法解決所有捨入誤差所帶來的問題。 您不妨找個計算機、輸入一個正數,再按開方鍵若干次得到一個數,接著按同樣多次的平方鍵,看看會有什麼結果。 一般而言,結果會與輸入值相差不大,不過若按開方鍵次數足夠多,您會發現結果會愈來愈靠近1, 於是再平方回來時與原本的輸入就有很大的差距; 當然,一旦開方的結果變成1,不論如何平方都不可能得到原來的值。 這個現象在任何計算機、使用任何精確度都成立,所差的就是何時變成1而已。
我們也可以寫一個程式驗証這個現象;下面是程式的(部份)輸出,這個程式求2的平方根若干次,再把結果求平方相同次數。 首先,我們開方十次再平方十次,於是第十次開方得到1.0006771088:
0 2.0000000000 ……… 5 1.0218970776 ……… 10 1.0006771088
0 1.0006771088 ……… 5 1.0218964815 ……… 10 1.9999581575
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
更新紀錄