バージョンでTrunc結果が異なる

解決


APT  2018-11-14 17:29:07  No: 49594

Delphi2010(32ビット)のTrunc結果と、Delphi10.2(32ビット)をのTrunc結果が異なるという問題に悩んでおります。

*Delphi2010(32ビット)
  Trunc( 5 * 0.01 * 100 ) = 5

*Delphi10.2(32ビット)
  Trunc( 5 * 0.01 * 100 ) = 4

レジスタについて以下を参考に調査したところ、http://verifiedby.me/adiary/pub/kashi/image/201311/roundingmode.pdf

PC(浮動小数点演算精度)が2010だと53ビット、10.2だと64ビットとなっていました。
また直接的な原因とは異なりますが、ほかにもFPU設定には下記の違いがありました。
・IM(無効な数値許容)が2010だとOFF、10.2だとON
・ZM(ゼロ除算許容)が2010だとOFF、10.2だとON

Delphiのバージョンアップ時、何かこのような変更報告はありましたでしょうか?
またDelphi10.2のTrunc結果をDelphi2010と同じ結果に合わせることは可能なのでしょうか?

以上、よろしくお願いいたします。


通りすがり  2018-11-14 19:02:10  No: 49595

このへんでしょうか?

浮動小数点数制御ルーチン - RAD Studio
http://docwiki.embarcadero.com/RADStudio/Tokyo/ja/%E6%B5%AE%E5%8B%95%E5%B0%8F%E6%95%B0%E7%82%B9%E6%95%B0%E5%88%B6%E5%BE%A1%E3%83%AB%E3%83%BC%E3%83%81%E3%83%B3


APT  2018-11-14 23:31:36  No: 49596

自己解決しました。

https://www.oreilly.com/library/view/delphi-in-a/1565926595/re62.html

LoadLibrary() でDLLをインポートすると、
DLLのインポートテーブルをアドレス空間にマッピングしますが、
そのときDLLによっては(おそらくDLLの作り方による)、
浮動小数点コントロールワードの後始末をしないらしいです。

これを後始末するように対策したコードが、
SysUtils.SafeLoadLibrary() のようです。
http://docwiki.embarcadero.com/Libraries/Tokyo/ja/System.SysUtils.SafeLoadLibrary

必要なDLLをロードするSetStartApplication()の中で
この関数を使ってDLLをインポートするようにしたところ、
D2010とD10.2で浮動小数点の精度設定が等しくなりました。

以上、ご協力ありがとうございました。


Mr.XRAY  2018-11-17 22:11:34  No: 49597

>  Trunc( 5 * 0.01 * 100 ) = 4 

テストしてみました.
残念ですが,
質問された方の書き込みの内容については,私の理解が及ぶ範囲ではありません.
単に計算のテストをしただけです.ご了承ください.
動作確認は Windows 7 U64(SP1) + Delphi XE5(UP2) Pro VCL-32 です.

[テストプロジェクトのダウンロード] (EXE 付き)
http://mrxray.on.coocan.jp/Delphi/zip/QandA_18110003_Trunc.zip 

procedure TForm1.Button1Click(Sender: TObject);
var
  LInteger  : Integer;
  LIntegerB : Integer;
  LDouble   : Double;
begin
  Memo1.Lines.Clear;

  // 掲示板で書き込みがあった計算式のまま
  // この結果は 4.この計算以外の結果は全て 5 
  LInteger := Trunc(5 * 0.01 * 100);
  Memo1.Lines.Add(IntToStr(LInteger));

  // 数値を明示的に実数型とする場合
  LInteger := Trunc(5.0 * 0.01  * 100.0);
  Memo1.Lines.Add(IntToStr(LInteger));

  // 数値を大きい値順に計算する場合
  LInteger := Trunc(100 * 5 * 0.01);
  Memo1.Lines.Add(IntToStr(LInteger));

  // 計算結果の値を実数型の変数に代入してからTrunc関数の引数とする場合
  LDouble  := 5 * 0.01 * 100;
  LInteger := Trunc(LDouble);
  Memo1.Lines.Add(IntToStr(LInteger));

  // 各々の数値をそれぞれの型の変数に代入してからTrunc関数の引数とする場合
  LInteger  := 5;
  LIntegerB := 100;
  LDouble   := 0.01;
  LInteger := Trunc(LInteger * LDouble * LIntegerB);
  Memo1.Lines.Add(IntToStr(LInteger));
end;


APT  2018-11-20 07:37:49  No: 49598

Mr.XRAYさん、実験ありがとうございます。

私の言葉足らずで、お時間を無駄にして申し訳ありません。
私のソフトはアドバンスソフトウェア株式会社さんのExcelCreatorを利用しております。

このExcelCreatorのdllをLoadLibrary() でインポートすると、
Trunc( 5 * 0.01 * 100 ) = 4 
という現象が再現できます。
ExcelCreatorの後始末に問題があるのだと思います。

そこで以下のように修正する事で解決いたしました。
LoadLibrary( PChar( dllName ) );
  ↓
SafeLoadLibrary( PChar( dllName ) );

ちなみにSafeLoadLibrary()を利用しない場合でも、
一度Double型のローカル変数に格納後、Truncをすれば答えは5になりました。
Trunc(5 * 0.01 * 100); 
  ↓
ttt := 5 * 0.01 * 100;
Trunc(ttt); 

FPUのレジスター上ではなく、CPUのスタック領域に作成された為だと思います。

以上、よろしくお願いいたします。


Mr.XRAY  2018-11-23 05:03:28  No: 49599

残念ですが, 
質問された方の書き込みの内容については,私の理解が及ぶ範囲ではありません. 
また,現象が確認可能な具体的なコードもありませんので,
以下は実数値を扱う際の一般的な内容です.

コンピュータでは有限の桁数しか扱えません.
数学で言う無限小や無限大という概念はありません.
特に 0.003 等の小数点以下の値を正確に扱うことはできません.
その結果,計算結果が数学的な演算と異なることになります.
以下はそのことを確認するための簡単なサンプルです (上記と意味合いが少し違いますが).

procedure TForm1.Button1Click(Sender: TObject);
var
  R1 : Single;
begin
  R1 := 0.1;

  if R1 = 0.1 then begin
    MessageBox(Handle, '同じ値です', '結果', MB_ICONINFORMATION);
  end else begin
    MessageBox(Handle, '同じ値ではありません', '結果', MB_ICONWARNING);
  end;
end;

FPU には制御ワードというのがあります.
Delphi で実数値,つまり浮動小数点の計算を行う場合,
この FPU の制御ワードを変更することによって,演算の精度やエラーの制御ができます.

サードパーティが提供しているライブラリには,この制御ワードを変更するものがあります.
例えば OpenGL がそうです.
現在の制御ワードの値は Get8087CW 関数で確認できます.
( SafeLoadLibrary は,FPU の制御ワードを保持するライブラリの読み込みです)

[ 浮動小数点数制御ルーチン ]
http://docwiki.embarcadero.com/RADStudio/Tokyo/ja/%E6%B5%AE%E5%8B%95%E5%B0%8F%E6%95%B0%E7%82%B9%E6%95%B0%E5%88%B6%E5%BE%A1%E3%83%AB%E3%83%BC%E3%83%81%E3%83%B3

[ Linux で x86 アセンブラ(浮動小数点編)]
https://qiita.com/MoriokaReimen/items/8d3b0dddcc2a77ecdfa5

手前味噌ですが,興味があれば以下の記事を参考にしてください.

[ 890_計算誤差 ( 数値計算の誤差 ) と多倍長演算 ]
http://mrxray.on.coocan.jp/Delphi/plSamples/890_CalcError.htm


Mr.XARY  2018-11-24 09:53:25  No: 49600

既に解決済みなので,今回はもう必要はないと思いますが,
このような現象が発生した時は問題の切り分けが重要と考えています.

今回の場合で言えば,DLL をロードしています.
では,DLL をロードしない場合はどうなのか ?
ということを調べます.
これには,余計な機能が働かないように新規にプロジェクトを作成することになります.
そのためにテスト用のプロジェクトを UP しました.

それを問題となっている Delphi 2010 と Delphi 10.2 で確認するわけです.
その後に DLL をロードするコードを追加して調べることになります.

上記のようなことが確認できれば,他の方にも参考になるのではないかと思っています.


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








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