TIBSQLで正確な数値が取得できない件について

解決


morutin  2007-08-24 16:59:28  No: 27510  IP: 192.*.*.*

いつもお世話になっています。
現在下記のようなことで行き詰まってます。
もしかしたら、基本的な部分を見落としているのかもしれませんが、どうぞよろしくお願いします。

[環境]
Delphi7
FireBird 1.5.3

下記のように、TClientDataSetへTIBSQLで取得した数値を書き込み、その後XMLで出力するという処理を行うと、
データベースに記憶されている値が”0.56”の時に正確な値が入ってくれません。他の”0.55”、”0.57”では正確な値がXMLへ出力されます。

  〜〜(省略)〜〜
  
  ClientDataSet.Edit;
  ClientDataSet.FieldByName('Suu').AsFloat := IBSQL.FieldByName('Num').AsDouble;
  ClientDataSet.Post;

  ClientDataSet.SaveToFile('C:\Data.xml', dfXMLUTF8);

_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
どこが、おかしいのか調べるためにTIBSQLを使って、値を取得するところで下記のようにしてみるとデータベースからの取得する時点で正確な値が取得できていません。

var
  D: Double;

  D := 0.56;
  D := IBSQL.FieldByName('Num').AsDouble - D;    ←  これは、変数"D"には"0"が入る。

  D := IBSQL.FieldByName('Num').AsDouble - 0.56; ←  こちらは、変数"D"には誤差が入る。(1.2445654564E-14 のような)
 
また、次のSQLを発行してみると結果は"0"が返ってきますので、データベースが保持している値は正確な値なような気がするのですが、、、
  SELECT Num - 0.56 FROM DataTable;    (”Num”は、問題の値が保持されているフィールド名)


最悪は、仕様上許される範囲で丸めを行おうと思っていますが、どうしても気持ちが悪いです。。
何故、このようになるのか教えて下さい。よろしくお願いします。

編集 削除
morutin  2007-08-24 18:27:30  No: 27511  IP: 192.*.*.*

記載忘れです。
"0.56"と取得されるところが"0.5600000000000001"になってしまいます。
よろしくお願いします。

編集 削除
HOta  2007-08-25 09:50:59  No: 27512  IP: 192.*.*.*

FireBird の対象項目の型をNumeric(9,2)にしたらどうでしょうか?

編集 削除
moru  2007-08-27 08:22:00  No: 27513  IP: 192.*.*.*

HOtaさんありがとうございます。
”DOUBLE PRECISION”から”Numeric(9,2)”へ型を変えてみましたが結果は同じでした。
できれば、FireBirdの項目の型を変更することは避けたいです。

編集 削除
HOta  2007-08-27 13:43:58  No: 27514  IP: 192.*.*.*

2進数による誤差ですから、どうしてもと言うなら、10進数の計算しか無いでしょう。

編集 削除
moru  2007-08-29 18:02:23  No: 27515  IP: 192.*.*.*

>2進数による誤差ですから
とのことからもう一度他の値について誤差が発生していないかを調べました所、
>他の”0.55”、”0.57”では正確な値がXMLへ出力されます。
と記載しましたが、あくまでXML出力時に丸められただけで期待する値は変数に保持されていませんでした。

いままでTIBSQLが勝手に演算していてくれているのかな程度しか考えていなかったたのですが、逆にとてもビットに対して正確な値を返そうとしていたのですね。
とても、勉強になりました。
今回は、それほど小数部の値が影響する処理ではないので仕様上の有効桁で四捨五入しようと思います。
HOtaさんありがとうございました。

編集 削除
通りすがり  2007-08-29 19:23:58  No: 27516  IP: 192.*.*.*

> D: Double;

浮動小数点を利用して計算をすると、どんな言語/DBでも必ず誤差が生じます。
(DelphiでもVBでもVC++でも)

計算をする場合は、整数型(Integer/long)や通貨型(Currency)などに移して
した方が良いかと思います。

編集 削除
morutin  2007-08-31 14:51:50  No: 27517  IP: 192.*.*.*

通りすがりさんありがとうございます。
今回は、計算処理後に値を丸めるので、2進数での演算による誤差は無視して処理をしようと思います。
今後、精度の必要な場面でその様に演算させたいと思っていますが、皆さんは小数を含む演算を行うときは必ず誤差が発生しないように処理をしてらっしゃいますか?それとも、ケースバイケースで対応してらっしゃいますか?
既に、解決済みにしてますがよろしくお願いします。

編集 削除
通りすがり  2007-08-31 17:20:34  No: 27518  IP: 192.*.*.*

Currency型は小数点以下4桁で固定されており、精度の良い会計計算が可能なため
事務処理などの処理は主に、CurrencyにDBのデータを読んで計算させています。
(そうしないと、小数点以下の数字があった場合、誤差が出て使い物になりません)

一方技術系計算の場合は、DoubleやExtendedなどの浮動小数点形式で計算を行っています。

編集 削除
moru  2007-08-31 17:42:59  No: 27519  IP: 192.*.*.*

通りすがりさん、ありがとうございます。
やはりその処理にあった型や計算順序を採用していかないとなんですね。

そのためには、設計をしっかりしないと、、
ありがとうございました。

編集 削除