演算結果の誤差に関して


DA  2009-12-16 18:14:57  No: 36442

演算結果の誤差に関して教えて下さい。

例えばIntegerのS1という変数があるとします。
今S1が30だとして
S1/2 =15
が答えですが、
S1/2=14.999999999・・・・
となってしまうこともあるのでしょうか?
とにかくなにか演算をしたときに上記のように15が理想の値なのに14.99999・・・・などといった現象はDelphiでは出ますか?
もし現象が出てしまうなら「こういったときにこのようになってしまう可能性がある。」というのを例など教えてもらえるとありがたいです。

またそうなってしまう場合の解決方法などを教えて下さい。

Delphiをはじめてからこのような現象は見たような見てないような感覚なので教えて下さい。
よろしくお願いします。


通りすがり  2009-12-16 18:39:18  No: 36443

これかいな?
http://www2.big.or.jp/~osamu/Delphi/tips.cgi?index=0299.txt


DA  2009-12-16 18:53:19  No: 36444

すいません。
浮動小数の演算についてあまり理解していませんでした。
過去ログ見ましたが、同じようなのが多くのっていました。
調べる前に質問してすいません。
とにかく浮動小数の演算はどうしても誤差が出てしまうのですね。

通りすがりさんのサイトにのっているCurrencyを使用すると、サイトの演算を行って、おまじないを入れない状態でも誤差は出ませんでした。
ただDouble型で同じ計算をすると誤差がでました。
対策としてはCurrencyを使用するのが無難なのでしょうか?
ただCurrencyを使用しても誤差は出てしまうのですよね?
(あまり大きい数字でなければでない?)

今は例えばTrunc(演算結果=3)
          Trunc(演算結果=2.99999・・・・)
となってしまった場合に3と2という結果になってしまうので、
          Trunc(演算結果+0.1)
などという対策をいれるようにしています。

過去ログとかを見ましたが、結局どういった対策をすべきでしょうか?

よろしくお願いします。


Mr.XRAY  2009-12-16 19:49:27  No: 36445

Mr.XRAYです.

この種の計算結果は,CPUと場合によってはコンパイラ(ここではDelphi)のバージョン
によって異なります.

また,結果を整数型として,あるいは実数型として取得したいのかによっても処理方法
が異なるでしょう.
実数型で取得したいようですので,Roundのかわりに

http://docwiki.embarcadero.com/VCL/ja/Special:Search?search=SimpleRoundTo&fulltext=Search

といくつか候補があります.
もし,お使いのDelphiのバージョンに上のリンクに掲載されている関数がなければ
自作することになるでしょう.


Mr.XRAY  2009-12-16 20:03:30  No: 36446

>Roundのかわりに

「Truncのかわりに」ですね.


にしの  2009-12-17 07:33:40  No: 36447

> Delphiをはじめてからこのような現象は見たような見てないような感覚なので教えて下さい。

C言語による最新アルゴリズム辞典のサンプルをDelphiに移植してみました。
Delphi2010での実行結果は、
Sum1が1.99999999999989
Sum2が2
となりました。

function Sum1(n: Integer; a: array of Double): Double;
var
  i: Integer;
  d: double;
begin
  d := 0;
  for i := 0 to n - 1 do d := d + a[i];
  Result := d;
end;

function Sum2(n: Integer; a: array of Double): Double;
var
  i: Integer;
  r, s, t: double;
begin
  //s は和, r は積み残し
  r := 0;
  s := 0;

  for i := 0 to n - 1 do
  begin
    r := r + a[i];    // 積み残し + 加えたい値
    t := s;           // 前回までの和
    s := s + r;       // 和を更新
    t := t - s;       // 実際に積まれた値の符号を変えたもの
    r := r + t;       // 積み残し
  end;
  Result := s;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
  a: array[0..10000] of double;
begin
  a[0] := 1;
  for i := 1 to 10000 do a[i] := 0.0001;
  //1 + 0.0001 + ... + 0.0001 = 2
  Memo1.Lines.Add(FloatToStr(Sum1(10001, a)));
  Memo1.Lines.Add(FloatToStr(Sum2(10001, a)));
end;


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

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






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