丸め誤差

1 つの実数から発生する誤差は、計算においては大きな問題ではないかもしれませんが、この誤差が原因で少なくとも 2 つの問題が生じます。第 1 に、正確な数値と見なした 2 つの値が等しいかどうかを検証する際に、これらの数値のいずれかまたは両方の浮動小数点表現に丸め誤差があり、それが原因で比較が成功せずに、論理的に間違った結果を得る可能性があります。また、浮動小数点の計算を行っているときに、丸め誤差が累積して、数値の精度が無視できないほど低下することもあります。

数値結果を慎重に確認して、丸め誤差またはその影響を最小限に抑えるようにしてください。倍精度算術を使用したり、アルゴリズムを再構築することで、これらの問題が改善されることがあります。例えば、計算に線形データ項目の配列を使用する場合、個々の配列要素から各配列の平均値を引き、それらの配列の個々の要素を配列要素の標準偏差を基に正規化することで、数値の精度の低下を抑えることができます。

次のコードはシステムによってさまざまな形で実行され、n、x、および s には異なる結果が生成される可能性があります。また、-fp-model precise (Linux* および Mac OS* X) または /fp:precise (Windows*)、あるいは -fp-model fast (Linux および Mac OS X) または /fp:fast (Windows) コンパイラー・オプションを使用すると、異なる結果が生成されます。0.2 の浮動小数点表現が正確ではないため、丸め誤差がまず x に累積され、その後 s に累積されます。その結果、n の最終的な値に影響を及ぼします。

  INTEGER n

  REAL s, x

  n = 0

  s = 0.0

  x = 0.0

1 n = n + 1

  x = x + 0.2

  s = s + x

  IF ( x .LE. 10. ) GOTO 1 ! Will you get 51 cycles?

  WRITE(*,*) 'n = ', n, '; x = ', x, '; s = ', s

この例では、一般的に発生するコーディング上の問題を示しており、浮動小数点変数が循環的に実行されるコード内で継続的に使用され、その変数の値が IF 文で検証されています。このようなプロセスは、数値積分計算で一般的に使用されます。この問題にはいくつかの解決方法があります。xs を整数インデックスの倍数として計算することができます。例えば、x に加算する文を「x = n * 0.2」に置き換えることで、丸め誤差の累積を避けることができます。また、循環的に実行されるコードの最後で整数インデックスに対して「IF (n <= 50) GOTO 1」のような検証を行うか、「DO n= 1,51」などの DO ループを使用することもできます。循環的に実行されるコード内で使用される実数変数に対して検証を行う必要がある場合、「IF (x <= 10.001)」などの現実的な許容誤差を使用することを推奨します。

浮動小数点算術は、必ずしも代数学の標準規則に従って行われているわけではありません。丸め誤差を考慮に入れた場合、加算の結合律は、厳密には成り立ちません。/fp:precise (Windows)、-fp-model precise (Linux および MacOS X)、または /assume:protect_parens (Windows)、-assume protect_parens (Linux および MacOS X) などのスイッチを使用すると、括弧を使って、誤差のない正確な答えを得るために必要な評価順序を表現することができます。加算の結合順序が予測不可能な場合があるため、生成されるコードを最適化する場合には、この方法を推奨します。

式「(x + y) + z」と「x + (y + z)」は、場合によっては異なる値の結果をもたらすことがあります。

コンパイラーは、コンパイル時にデフォルトの丸めモード (最も近い値に丸めるモード) を使用します。また、コンパイラーは、最適化レベルが上がるにつれ、より多くの演算をコンパイル時に行って、ランタイムの演算を減らします。丸めモードを別の (最も近い値に丸めるモード以外の) 設定にした場合、その丸めモードは、計算がランタイムで行われる場合にのみ使用されます。ランタイム時に計算を強制的に行うには、–fp-model strict (Linux または MacOS) オプションまたは /fp:strict (Windows) オプションを使用します。

関連情報