浮動小数点比較ルーチン
浮動小数点ルーチンの使用 への移動
浮動小数点数の比較に関して、自明でない問題がいくつかあります。
浮動小数点比較の問題点
浮動小数点比較の問題点の 1 つは、数の表示とメモリ内での表現が厳密には一致しないことです。X := 99.9999964
や Y := 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 ユニットには、浮動小数点比較を扱う関数 CompareValue、SameValue、IsZero が用意されています。これらの関数は、次のように宣言されています。
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;
ここで、A
と B
は比較する浮動小数点数、Epsilon
は A
と B
が異なっていても同じ値と見なせる許容差です。
Epsilon = 0
が指定された場合、何らかの妥当なデフォルト値が暗黙に使用されます。たとえば、SameValue の Extended バージョンは、デフォルト値を使用します:
Epsilon = Max(Min(Abs(A), Abs(B)) * 1E-16, 1E-16)