浮動點計算中的精確度和精確度

注意

Office 365 專業增強版 即將重新命名為 Microsoft 365 企業版應用程式。 如需關於此變更的詳細資訊,請閱讀此部落格文章

原始 KB 編號:  125056

摘要

在許多情況下,浮動點計算中的精確度、舍入和精確度可用於產生對程式師令人吃驚的結果。 它們應該遵循四個一般規則:

  1. 在包含 single 和 double 精確度的計算中,結果的精確度通常不會是單一精度。 如果需要雙精度,請務必確定計算中的所有字詞(包括常數)都是以 double 精度指定。

  2. 永遠不會假設電腦已正確表示簡單數值。 大部分的浮點數值都無法以有限的二進位值完全表示。 例如, .1 .0001100110011... 在二進位中(它會永遠重複),所以無法使用二進位演算法(包括所有的電腦)在電腦上以完全準確性表示。

  3. 永遠不會假設結果正確地指向最後的小數位位置。 「True」答案和可用的任何浮點數處理單位的有限精確度,都有一定的細微差異。

  4. 永遠不會比較兩個浮點值,以查看它們是否相等或不相等。 這是規則3的 corollary。 在 "應該" 的數位之間,幾乎永遠不會有細微差異。 相反地,請務必檢查號碼是否幾乎相等。 換句話說,請檢查兩者之間的差異是否很小或沒有意義。

其他相關資訊

一般會將上述規則套用至所有語言,包括 C、c + + 和組合語言程式。 下列範例會示範使用 FORTRAN PowerStation 的一些規則。 所有的範例都是以 FORTRAN PowerStation 32 編譯,但不含任何選項,除非最後一個是以 C 撰寫的。

範例 1

第一個範例會示範兩件事:

  • 根據預設,FORTRAN 常數預設為單一精度(預設為 C 常數是雙精度)。
  • 包含任何單一精確度字詞的計算方式,不會比計算中的所有字詞,都是單一精度高。

在使用1.1 (單一精度常數)初始化後,y 不准確為單一精度變數。

x = 1.100000000000000  y = 1.100000023841858

以精確的雙精度值乘以單一精度值的結果,會接近兩個單一精度值的乘積。 這兩種計算都有數以千計的錯誤的次數乘以兩個雙精度值。

true = 1.320000000000000 (multiplying 2 double precision values)
y    = 1.320000052452087 (multiplying a double and a single)
z    = 1.320000081062318 (multiplying 2 single precision values)

範例程式碼

C Compile options: none

       real*8 x,y,z
       x = 1.1D0
       y = 1.1
       print *, 'x =',x, 'y =', y
       y = 1.2 * x
       z = 1.2 * 1.1
       print *, x, y, z
       end

範例 2

範例2使用二個方程式。 它會示範均勻的精確度計算不是理想的,而且計算結果必須先測試,然後才會因小型錯誤可能會產生大量的結果。 範例2中平方根函數的輸入只有略微為負值,但仍然無效。 如果雙精度計算沒有輕微的錯誤,則結果會是:

Root =   -1.1500000000

相反地,它會產生下列錯誤:

執行階段錯誤 M6201: MATH

  • sqrt:網域錯誤

範例程式碼

C Compile options: none

       real*8 a,b,c,x,y
       a=1.0D0
       b=2.3D0
       c=1.322D0
       x = b**2
       y = 4*a*c
       print *,x,y,x-y
       print "(' Root =',F16.10)",(-b+dsqrt(x-y))/(2*a)
       end

範例3

範例3顯示由於優化未開啟時進行優化,所以值可能會暫時保留比預期更高的精確度,而且會 unwise 來測試兩個浮點值是否相等。

在此範例中,兩個值都相等且不相等。 在第一個 IF 中,Z 的值仍在副處理器的堆疊上,且具有與 Y 相同的精度。因此 X 不等於 Y,第一筆訊息會列印出來。在第二個 IF 時,必須從記憶體載入 Z,因此,其精確度和值與 X 相同,也會列印第二封郵件。

範例程式碼

C Compile options: none

       real*8 y
       y=27.1024D0
       x=27.1024
       z=y
       if (x.ne.z) then
         print *,'X does not equal Z'
       end if
       if (x.eq.z) then
         print *,'X equals Z'
       end if
       end

範例4

範例程式碼4的第一部分計算兩個接近1.0 的數位可能的最小差異。 這是因為在1.0 的二進位表示中新增了單一位。

x   = 1.00000000000000000  (one bit more than 1.0)
y   = 1.00000000000000000  (exactly 1.0)
x-y =  .00000000000000022  (smallest possible difference)

有些的 FORTRAN 版本會在顯示這些數位時對其進行舍入,使其固有的數值 imprecision 不太明顯。 這就是顯示時 x 和 y 的外觀。

範例程式碼4的第二部分會計算兩個接近10.0 的數位可能的最小差異。 同樣地,它會將單一位新增至10.0 的二進位標記法來執行這項動作。 請注意,接近10的數位之間的差異大於接近1的差值。 這會示範數值的絕對值較大的一般原則,可在指定的位數中儲存的值越少。

x   = 10.00000000000000000  (one bit more than 10.0)
y   = 10.00000000000000000  (exactly 10.0)
x-y =   .00000000000000178

也會顯示這些數位的二進位表示,以顯示它們的差異只是1位。

x = 4024000000000001 Hex
y = 4024000000000000 Hex

範例程式碼4的最後一部分會顯示簡單的非重複十進位值,通常只能使用重複分數以二進位表示。 在此情況下 x = 1.05,這需要重複的因素 CCCCCCCC ...。(十六進位)中的尾數。 在 FORTRAN 中,最後一個數位 "C" 會舍入為 "D",以維持最高的準確性:

x = 3FF0CCCCCCCCCCCD (Hex representation of 1.05D0)

即便是四捨五入後,結果也不會完全準確。 在最低有效的數位後會出現一些錯誤,我們可以移除第一個數位以加以看到。

x-1 = .05000000000000004

範例程式碼

C Compile options: none

       IMPLICIT real*8 (A-Z)
       integer*4 i(2)
       real*8 x,y
       equivalence (i(1),x)

       x=1.
       y=x
       i(1)=i(1)+1
       print "(1x,'x  =',F20.17,'  y=',f20.17)", x,y
       print "(1x,'x-y=',F20.17)", x-y
       print *

       x=10.
       y=x
       i(1)=i(1)+1
       print "(1x,'x  =',F20.17,'  y=',f20.17)", x,y
       print "(1x,'x-y=',F20.17)", x-y
       print *
       print "(1x,'x  =',Z16,' Hex  y=',Z16,' Hex')", x,y
       print *

       x=1.05D0
       print "(1x,'x  =',F20.17)", x
       print "(1x,'x  =',Z16,' Hex')", x
       x=x-1
       print "(1x,'x-1=',F20.17)", x
       print *
       end

範例5

在 C 中,浮動常數預設會加倍。 使用 "f" 表示浮點值,如 "89.95 f"。

/* Compile options needed: none
*/ 

#include <stdio.h>

void main()
   {
      float floatvar;
      double doublevar;

/* Print double constant. */ 
      printf("89.95 = %f\n", 89.95);      // 89.95 = 89.950000

/* Printf float constant */ 
      printf("89.95 = %f\n", 89.95F);     // 89.95 = 89.949997

/*** Use double constant. ***/ 
      floatvar = 89.95;
      doublevar = 89.95;

printf("89.95 = %f\n", floatvar);   // 89.95 = 89.949997
      printf("89.95 = %lf\n", doublevar); // 89.95 = 89.950000

/*** Use float constant. ***/ 
      floatvar = 89.95f;
      doublevar = 89.95f;

printf("89.95 = %f\n", floatvar);   // 89.95 = 89.949997
      printf("89.95 = %lf\n", doublevar); // 89.95 = 89.949997
   }