浮動小数点値の等号判断
86 FloatIsName 動作確認 Delphi2007 更新日 2008/02/20(水)

Delphiでは浮動小数点を表す際にいくつかの型があります。

これを比較する場合、以下のようにしてみました

    procedure TForm1.Button1Click(Sender: TObject);
    const
      A: Double  = 1.1;
      B: Extended= 1.1;
    begin
      if A=B then ShowMessage('イコール')
             else ShowMessage('ノットイコール');
    end;

すると、ノットイコールが表示されます。
変ですね。これはどういう事かというと
二進数では1/10は割り切れないという
コンピュータの問題が発生しているそうです。

2chで教えてもらったものを引用します。
> 10進数で説明しよう。 
> 1/3 を小数点以下4桁まで格納できる変数と 
> 小数点以下8桁まで格納できる変数に代入すると 
> 0.3333 と 0.33333333 になるな。 
> 
> この値が 1/3 から生成されたことをいったん忘れて、変数の 
> 値を比較すると 0.33330000 と 0.33333333 を比較することになるな。 
> 
> となればこの二つを異なる物と判断すべきだね? 
> 
> だから理屈はあるし、精度の異なる数値を直接比較してはいけない。 
> 
> 一律に「浮動小数点を=で判定することが間違ってる」といってはいけないのだが 
> そのためにはちゃんとした誤差評価が必要になるので多くの場合は 
> 差が「計算機イプシロン」と呼ばれる非常に小さな数より小さければ同じ物と 
> 見なす。 

ということだそうです。
D6にはこれに対応した
SameValue/IsZero/CompareValueという
関数があり、これを使えば浮動小数点値でも等号が判断できるようです。

似たようなものを自作するとすると
こうなります。
────────────────────
{-------------------------------
//  IsSame
機能:       適当に小さい誤差eの範囲内で
            浮動小数点値が等しいかどうかを調べる関数
引数説明:   A, B: 比較対照
            e: ±の誤差
戻り値:     true:等しい false:等しくない
備考:       Math.SameValueと同じ機能かも
履歴:       2001/09/05
//------------------------------}
function IsSame(A, B: Extended; e: Extended=0): Boolean;
begin
  Result := Abs(A-B) <= e;
end;
//------------------------------

procedure Check(A, B: Boolean; Msg: String = ''); overload;
begin
  if A <> B then
    raise Exception.Create('Checkエラー'+ Msg);
end;

procedure testIsSame;
begin
  Check(True,  IsSame(1001.0, 999.0, 10));
  Check(False, IsSame(1001.0, 999.0, 1));
  Check(True,  IsSame(0.19999900, 0.20000000, 0.01));
  Check(True,  IsSame(0.19999900, 0.20000000, 0.001));
  Check(True,  IsSame(0.19999900, 0.20000000, 0.0001));
  Check(True,  IsSame(0.19999900, 0.20000000, 0.00001));
  Check(True,  IsSame(0.19999900, 0.20000000, 0.000001));
  Check(False, IsSame(0.19999900, 0.20000000, 0.0000001));
  Check(False, IsSame(0.19999900, 0.20000000, 0.00000001));

  Check(True,  IsSame(0.199999, 0.200, 0.01));
end;
────────────────────

これでAがeという誤差の範囲内でBと一致しているかを判断できます。
eには適当にその場で必要な
ゼロに近い小さい値を入れておくのがよいでしょう。

例えば結果Aと結果Bが0.2で一致するという事なら

誤差を一桁さげて0.01として
IsSame(A, B, 0.01)とすると
A=0.199999 B=0.20001111 であっても同じ値だと判断できます。


最初に挙げた
型の違いによる等号の不一致も
    procedure TForm1.Button1Click(Sender: TObject);
    const
      A: Double  = 1.1;
      B: Extended= 1.1;
    begin
      if IsSame(A, B, 0.0001) then 
        ShowMessage('イコール')
      else 
        ShowMessage('ノットイコール');
    end;
このようにすると、無事に'イコール'が表示されるようになります。