Sun Studio 12: Fortran プログラミングガイド

6.5 数値に関連したその他の問題

この節では、無効演算、ゼロ除算、オーバーフロー、アンダーフロー、結果不正確の例外を予想外に生成する算術演算に関連する、より実際的な問題について説明します。

たとえば、IEEE 規格より前には、2 つの極めて小さな数をコンピュータで乗算すると、結果はゼロになっていました。メインフレームやミニコンピュータのほとんどが、この方式でした。これに対し、IEEE の算術演算では、段階的なアンダーフローが計算の範囲を動的に拡張します。

たとえば、マシンのイプシロン、つまり表現可能な最小値が 1.0E-38 である 32 ビットプロセッサを想定し、2 つの小さな数値を乗算してみます。


      a = 1.0E-30
      b = 1.0E-15
      x = a * b

旧式の算術演算では、0.0 になりますが、IEEE の算術演算では (同じワード長で) 結果は 1.40130E-45 となります。アンダーフローは、マシンが本来表せる値よりも小さい結果になったということを示します。この結果は、いくつかのビットが仮数部から「盗まれ」、指数部へシフトされたことによってできました。 結果 (非正規化数)は、ある場合には精度が低くなりますが、逆に高くなる場合もあります。これに関する詳細な説明はこのマニュアルの範囲外です。詳細に興味のある方は、1980 年 1 月に発行された『Computer』誌 (第 13 巻 No.1)、特に J. Coonen 氏の論文「Underflow and the Denormalized Numbers」を参照してください。

科学技術プログラムのほとんどは、方程式を解いたり、行列の因数分解など、丸めに敏感に反応するコードセクションがあります。段階的なアンダーフローがなければ、プログラマは不正確なしきい値への接近を検出する独自の方法を実装します。実装できなければ、独自のアルゴリズムの安定した実装はあきらめるかしかありません。

これらの話題に関する詳細は、『数値計算ガイド』を参照してください。

6.5.1 単純なアンダーフローを防ぐ

アプリケーションの中には、実際に、非常にゼロに近いところで多くの処理をしているものがあります。これは、誤差や差分補正のアルゴリズムでよく発生します。数値的に安全で最大のパフォーマンスを得るには、重要な演算は拡張精度で行うべきです。アプリケーションが単精度の場合は、重要な演算を倍精度にすればかまいません。

例: 単精度の、簡単なドット積の演算


      sum = 0
      DO i = 1, n
         sum = sum + a(i) * b(i)
      END DO

a(i)b(i) が極めて小さい数であれば、多数のアンダーフローが発生することになります。演算を倍精度に移行すると、ドット積をより正確に計算でき、アンダーフローもなくなります。


      DOUBLE PRECISION sum
      DO i = 1, n
         sum = sum + dble(a(i)) * dble(b(i))
      END DO
      result = sum

アンダーフローに関して、SPARC プロセッサを旧式のシステムのように動作 (Store Zero) させることができます。このためには、ライブラリルーチン nonstandard_arithmetic() への呼び出しを追加するか、アプリケーションの主プログラムを -fns オプションを付けてコンパイルします。

6.5.2 間違った答えのまま継続する

結果が明らかに間違っている場合でも処理が継続されることを、疑問に思われるかもしれません。IEEE の算術演算では、NaNInf など、どの種別の間違った答えを無視できるかをユーザーが指定できます。そして、そのような区別に基づいて決定が行われます。

たとえば、回路シミュレーションを想定してみましょう。特有の 50 行の演算の中で、引数の目的となる重要な変数は電圧量だけであり、この変数のとり得る値は +5v、0、-5v だけであると仮定します。

途中の演算結果が正当な範囲にくるように、演算の各部分を詳細に調整できます。

さらに、Inf は許可されない値であるので、大きな数を乗算しないような特別なロジックが必要です。

IEEE 算術演算ではロジックはかなり単純にできます。演算を明確な形式で記述し、最終結果を正しい値に導くだけでかまいません。 なぜなら、Inf の発生する可能性があり、それは簡単にテストできるからです。

さらに、0/0 の特殊なケースも検出でき、ユーザーの望む形で処理できます。不必要な比較を行わなくてもすむため、結果は読みやすく、実行も高速です。

6.5.3 アンダーフローの頻発

2 つの極めて小さな数を乗算すると、結果はアンダーフローとなります。

あらかじめ、乗算 (または減算) の被演算子が小さくなり、アンダーフローしそうであることがわかっていれば、計算を倍精度で行い、そのあとで結果を単精度に変換します。

たとえば、ドット積ループは次のようになります。


  real sum, a(maxn), b(maxn)
  ...
  do i =1, n
      sum = sum + a(i)*b(i)
  enddo

a(*)b(*) は、小さな要素が入ることがわかっているので、倍精度で実行し、数値の正確性を保持します。


  real a(maxn), b(maxn)
  double sum
  ...
  do i =1, n
      sum = sum + a(i)*dble(b(i))
  enddo

このようにすることによって、オリジナルのループが原因であるアンダーフロー頻発をソフトウェア的に解決し、パフォーマンスを上げることができます。しかし、これに関しては、確実で手間のかからない方法はありません。計算が多いコードを使用するユーザー自身の経験のほうが、より適切な解決策を決定することでしょう。