浮動小数点比較ルーチン

提供: RAD Studio
移動先: 案内検索

浮動小数点ルーチンの使用 への移動


浮動小数点数の比較に関して、自明でない問題がいくつかあります。

浮動小数点比較の問題点

浮動小数点比較の問題点の 1 つは、数の表示とメモリ内での表現が厳密には一致しないことです。X := 99.9999964Y := 100.000003 のような数を考えましょう。これらはどちらも 100 と表示されますが、両者は等しくありません。

 uses
   System.SysUtils;
 var X, Y: Single;
 begin
   X := 99.9999964;
   Y := 100.000003;
   Writeln('X =', FloatToStr(X));
   Writeln('Y =', FloatToStr(Y));   ReadLn;
 end.

このプログラムの出力は次のようになります。

X = 100
Y = 100

しかし、X と Y を比較しようとすると、両者は等しくありません。 逆の例を次に示します。

 var X:Single;
 begin
   X:=0.1;
   Writeln('X =', FloatToStr(X));
 end.

このプログラムの出力は次のようになります。

X = 0.100000001490116

もう 1 つの問題は、浮動小数点数の有限表現に起因します。浮動小数点数を "それ自身" と比較してみましょう。

 var X:Single;
 begin
  X:=0.1;
  if X=0.1 then
   Writeln('Equal')
  else
   Writeln('Not equal');
  ReadLn;
 end.

このプログラムの出力は次のようになります。

Not equal

なぜでしょうか。これを理解するには、正確な小数 0.1 は 2 進数では無限循環小数 0.0(0011) として表現されることを思い出してください。 単精度(Single 型)の X では、仮数部は 23 ビットしかありません。Delphi では、拡張精度(Extended 型)を使って、すべての浮動小数点演算を実行します。Delphi で単精度(Single 型)の X が、63 ビットの仮数部を持つ拡張精度(Extended)表現に変換されると、プログラムではすべての余分なビットを、(実際のビット 0.0(0011) ではなく)単にゼロで初期化します。詳細については、「有限精度のもたらす影響」を参照してください。

では、単精度(Single 型)と倍精度(Double 型)の 0.1 を比較してみましょう。

 var
    X:Single;
    Y:Double;
 begin
  X:=0.1;
  Y:=0.1;
  if X=Y then
   Writeln('Equal')
  else
   Writeln('Not equal');
  ReadLn;
 end.

このプログラムの出力は次のようになります。

Not equal

やはり、0.1 の Single 表現と Double 表現では、仮数部のビット数が異なります。

比較ルーチン

浮動小数点数の正しい比較を実現する解決策は、何らかの小さい許容誤差 Epsilon を使用することです。2 つの浮動小数点数の差が Epsilon の許容範囲内に収まる場合、両者は等しいと見なされます。

System.Math ユニットには、浮動小数点比較を扱う関数 CompareValueSameValueIsZero が用意されています。これらの関数は、次のように宣言されています。

 function CompareValue(const A, B: Extended; Epsilon: Extended): TValueRelationship;
 function SameValue(const A, B: Extended; Epsilon: Extended): Boolean;
 function IsZero(const A: Extended; Epsilon: Extended): Boolean;

ここで、AB は比較する浮動小数点数、EpsilonAB が異なっていても同じ値と見なせる許容差です。

Epsilon = 0 が指定された場合、何らかの妥当なデフォルト値が暗黙に使用されます。たとえば、SameValueExtended バージョンは、デフォルト値を使用します:

Epsilon = Max(Min(Abs(A), Abs(B)) * 1E-16, 1E-16)

関連項目