計算結果を正しく求めるには?

解決


バタースカッチ  2003-10-10 23:29:09  No: 5169

初めまして。
次のような計算を行った場合

function A(s: Extended): Variant;
begin
  Result := s;
end;

var
  d1, d2: double;
begin
  d1 := double( A(10.2) );
  d2 := double( A(100) );
  ShowMessage( IntToStr(Trunc( d1 * d2 )) );
end;

計算結果が "1020" になるところ "1019"になって返ってしまいます。
Variant変数とTruncを絡めた計算であり、ある一部の値の組み合わせで
正しい結果が得られなくなります。
なぜだか教えて頂けないでしょうか。宜しくお願い申し上げます。


masayan  2003-10-11 00:23:15  No: 5170

とりあえず...

  ShowMessage( IntToStr(Round( d1 * d2 )) );
もしくは...
  ShowMessage( FormatFloat('#',d1 * d2));

で正しい結果になると思います。

これは、実数型であればDelphiに限らず起こる現象です。

d1 * d2は、1020ですが内部では1019.9999...となっています。
Truncは、それを単純に整数に変換するため1019となります。
Roundは、それの近似値を計算して整数に変換するので1020となります。

d1,d2をExtended型にすると精度が高くなるので回避できるかもしれませんがVariantに代入すると内部的にいったんDoubleに変換されるようなのでダメみたいです。(推測です。)

実数型は、多かれ少なかれ誤差を生じるものなので注意してください。可能であれば整数型が金額型を使ったほうが良いと思います。


Halbow  2003-10-11 00:40:06  No: 5171

Halbow です。

> Variant変数とTruncを絡めた計算であり、

Variant は関係ありません。

procedure TForm1.Button1Click(Sender: TObject);
var
  d1, d2: double;
begin
  d1 := 10.2;
  d2 := 100;
  ShowMessage( IntToStr(Trunc( d1 * d2 )) );
  ShowMessage(Format('%.14f',[d1*d2]));
end;

これでなぜ、Trunc() で 1019 になるか分かります。
ふつうは、もっとも近い整数にするのなら Round() を
使うと思います。


バタースカッチ  2003-10-11 00:43:55  No: 5172

ご意見ありがとうございます。

実数型をそのままTrunc関数に放り込みことが危険だということは
承知いたしました。

ただ目的は切捨てで、変数には先ほどの値が入るとは限らないのです。
ですので別の方法を考えないといけないという結論になりました。

とりあえず、実数型を別の型に変換してからTrunc関数を使うように
してみますが、もし他に良い方法があればご教授のほう宜しくお願いします。


バタースカッチ  2003-10-11 01:16:16  No: 5173

結果以下の関数で対処することにしました。
ありがとうございました。

function _Trunc(X: Extended): Integer;
var
  s: String;
begin
  s := FloatToStr(X);
  Result := Trunc(StrToFloat(s));
end;


masayan  2003-10-11 01:38:42  No: 5174

これでどうでしょう?

function A(s: Extended): Variant;
begin
  Result := s; // 文字列に変換した方が良いかも?
end;

var
  d1, d2: Extended;
begin
  d1 := StrToFloat(A(10.2));
  d2 := StrToFloat(A(100));
  ShowMessage( IntToStr(Trunc( d1 * d2 )) );
end;

d1,d2をExtendedにしてA()を文字列と見なしStrToFloatで変換しています。

ちなみに
  d1 := Extended(A(10.2));
とするとd1には10.2が入りません。

ということは、A(10.2)の戻り値は、Extended型で10.2が正しく入っているようなのでExtendedにキャストするときに 
  Extended (10.2)
    ↓(ここで丸め誤差発生)
  Double (10.199999...)
    ↓
  Extended (10.199999...)
という処理をしているようです。

で、StrToFloatを使うと
  Extended (10.2)
    ↓(丸め誤差は発生しない)
  String ("10.2")
    ↓
  Extended (10.2)
正しくD1に代入できるようです。


Halbow  2003-10-11 03:23:37  No: 5175

Halbow です。

> function _Trunc(X: Extended): Integer;
> var
>   s: String;
> begin
>   s := FloatToStr(X);
>   Result := Trunc(StrToFloat(s));
> end;

ふーむ、SysUtils.pas を読むと、文字列 <-> 浮動小数点 の変換は、結局
指定された桁数で丸めているだけでは? これと Trunc() を使いたいという
のはどこか矛盾しているように思います。 例えば、0.0000000001 を加えて
から Trunc() する方が理にかなっているかも知れませんね。


にしの  2003-10-11 04:46:18  No: 5176

少数以下が1桁でしたら、固定小数点型のCurrency型を使えば良いかと思います。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加