浮動小数点計算の精度と精度

元の KB 番号: 125056

概要

浮動小数点計算の精度、丸め、精度が、プログラマにとっては驚くほどの結果を生成するために機能する場合は、多くの状況があります。 次の 4 つの一般的な規則に従う必要があります。

  1. 単精度と倍精度の両方を含む計算では、結果は通常、単精度以上の精度ではありません。 倍精度が必要な場合は、計算のすべての項 (定数を含む) が倍精度で指定されていることを確認してください。

  2. 単純な数値がコンピューターで正確に表されているとは想定しないでください。 ほとんどの浮動小数点値は、有限バイナリ値として正確に表すことはできません。 たとえば、 .1 バイナリ .0001100110011... (永遠に繰り返されます) であるため、すべての PC を含むバイナリ算術演算を使用して、コンピューター上で完全な精度で表すことはできません。

  3. 結果が最後の小数点以下の桁数に正確であるとは想定しないでください。 "true" の回答と、浮動小数点演算ユニットの有限精度で計算できる内容には、常に小さな違いがあります。

  4. 2 つの浮動小数点値を比較して、等しいか等しくないかを確認しないでください。 これはルール 3 の要です。 ほとんどの場合、"はい" が等しい数値の間に小さな違いがあります。 代わりに、常にチェックして、数値がほぼ等しいかどうかを確認します。 言い換えると、チェック、それらの違いが小さいか、重要であるかどうかを確認します。

詳細情報

一般に、上記の規則は、C、C++、アセンブラーを含むすべての言語に適用されます。 次のサンプルは、FORTRAN PowerStation を使用する規則の一部を示しています。 すべてのサンプルは、オプションなしで FORTRAN PowerStation 32 を使用してコンパイルされました。ただし、最後のサンプルは C で記述されています。

サンプル 1

最初のサンプルでは、次の 2 つのことを示します。

  • この FORTRAN 定数は、既定では単精度です (C 定数は既定では倍精度です)。
  • 単精度項を含む計算は、すべての項が単精度である計算よりも正確ではありません。

1.1 (単精度定数) で初期化された後、y は単精度変数と同じくらい不正確です。

x = 1.100000000000000  y = 1.100000023841858

単精度値に正確な倍精度値を乗算した結果は、2 つの単精度値を乗算するのとほぼ同じくらい悪くなります。 どちらの計算にも、2 つの倍精度値を乗算する数千倍の誤差があります。

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 次方程式を使用します。 これは、倍精度の計算でさえも完璧ではないことを示し、小さなエラーが劇的な結果を得ることができるかどうかに依存する前に、計算の結果をテストする必要があることを示しています。 サンプル 2 の平方根関数への入力はわずかに負ですが、それでも無効です。 倍精度計算にわずかなエラーがない場合、結果は次のようになります。

Root =   -1.1500000000

代わりに、次のエラーが生成されます。

実行時エラー M6201: MATH

  • sqrt: DOMAIN エラー

サンプル コード

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 は、最適化がオンになっていない場合でも発生する最適化により、値が一時的に予想よりも高い精度を保持する可能性があり、2 つの浮動小数点値の等価性をテストするのは賢明ではないことを示しています。

この例では、2 つの値はどちらも等しく、等しくないです。 最初の IF では、Z の値はコプロセッサのスタック上にあり、Y と同じ精度を持ちます。したがって、X は Y と等しくなく、最初のメッセージが出力されます。2 番目の IF の時点では、Z はメモリから読み込まれなければならなかったため、X と同じ精度と値を持ち、2 番目のメッセージも出力されます。

サンプル コード

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 に近い 2 つの数値の最小差を計算します。 これは、1.0 のバイナリ表現に 1 ビットを追加することで行います。

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

FORTRAN の一部のバージョンでは、数値を表示するときに数値を丸めて、固有の数値の不正確さがそれほど明白でないようにします。 これが、x と y が表示されたときに同じように見える理由です。

サンプル コード 4 の 2 番目の部分では、10.0 に近い 2 つの数値の最小差を計算します。 ここでも、10.0 のバイナリ表現に 1 ビットを追加することでこれを行います。 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 の最後の部分では、単純な繰り返しでない 10 進値は、多くの場合、繰り返し分数によってのみバイナリで表すことができることを示しています。 この場合、x=1.05。繰り返し係数 CCCCCCCC.... が必要です。(16 進) を仮数で指定します。 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 では、浮動小数点定数は既定で 2 倍になります。 "89.95f" のように、"f" を使用して float 値を示します。

/* 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
   }