18

Delphi 2009 でコンパイルして実行すると、このコンソール アプリケーションは「奇妙な」と書き込みます。「より小さい」演算子の両側の値は等しいですが、コードは等しくないかのように動作します。この問題を回避するにはどうすればよいですか?

program Project5;

{$APPTYPE CONSOLE}

var
  C: Currency;
begin
  C := 1.32;

  if C < 1.32 then
  begin
    WriteLn('strange');
  end;

  ReadLn;
end.

ps コードは他の値でも問題なく動作します。

Barry Kelly によるこの回答では、Currency 型は「浮動小数点コードと同じように精度の問題の影響を受けにくい」と説明されています。

4

4 に答える 4

9

これは、Delphi での回帰のように見えます。

Delphi 2010 では出力が「奇妙」ですが、XE2 では出力がないため、バグは存在しません。テストする XE は手元にありませんが、XE も「奇妙な」出力をすることを確認してくれた @Sertac に感謝します。古いバージョンの Delphi も問題ないため、これは D2009 頃の回帰であることに注意してください。

2010年に生成されたコードは次のとおりです。

Project106.dpr.10: if C < 1.32 then
004050D6 DB2D18514000     fld tbyte ptr [$00405118]
004050DC DF2D789B4000     fild qword ptr [$00409b78]
004050E2 DED9             fcompp 
004050E4 9B               wait 
004050E5 DFE0             fstsw ax
004050E7 9E               sahf 
004050E8 7319             jnb $00405103
Project106.dpr.12: WriteLn('strange');

リテラル 1.32 は、値 13200 を持つ 10 バイトの浮動小数点値として格納されます。これは正確に表現可能なバイナリ浮動小数点値です。10 バイト浮動小数点数として格納される 13200 のビット パターンは次のとおりです。

00 00 00 00 00 00 40 CE 0C 40

ただし、リテラルの $00405118 に格納されているビット パターンは異なり、 よりわずかに大きくなって13200います。値は次のとおりです。

01 00 00 00 00 00 40 CE 0C 40

C < 1.32そして、それが評価される理由を説明していTrueます。

XE2 で生成されるコードは次のとおりです。

Project106.dpr.10: if C < 1.32 then
004060E6 DF2DA0AB4000     fild qword ptr [$0040aba0]
004060EC D81D28614000     fcomp dword ptr [$00406128]
004060F2 9B               wait 
004060F3 DFE0             fstsw ax
004060F5 9E               sahf 
004060F6 7319             jnb $00406111
Project106.dpr.12: WriteLn('strange');

ここで、リテラルが 4 バイトの float で保持されていることに注意してください。これは、 と比較してみるとわかりdword ptr [$00406128]ます。に格納されている単精度浮動小数点数の内容を見ると、次の$00406128ことがわかります。

00 40 4E 46

そして、それは 4 バイトの float として表される正確に 13200 です。

私の推測では、2010 年のコンパイラは、に直面したときに次のことを行い1.32ます。

  • 1.32 を最も近い正確に表現可能な 10 バイト浮動小数に変換します。
  • その値に 10000 を掛けます。
  • 結果の 10 バイト float を に格納し$00405118ます。

1.32 は正確に表現できないため、最終的な 10 バイトの float は正確に 13200 ではないことがわかります。おそらく、コンパイラがこれらのリテラルを 4 バイトの float に格納することから 10 バイトの float に格納するように切り替えたときに、回帰が発生しました。

基本的な問題は、データ型に対する Delphi のサポートがCurrency、まったく欠陥のある設計に基づいていることです。2 進浮動小数点演算を使用して 10 進固定小数点データ型を実装すると、問題が発生するだけです。設計を修正する唯一の正気の方法は、固定小数点整数演算を使用するようにコンパイラを完全に再設計することです。新しい 64 ビット コンパイラが 32 ビット コンパイラと同じ設計を使用していることに注意してください。

正直に言うと、Delphi コンパイラがCurrencyリテラルで浮動小数点演算を行うのを止めたいと思います。それは完全な地雷原です。頭の中で 10,000 シフトを次のように行います。

function ShiftedInt64ToCurrency(Value: Int64): Currency;
begin
  PInt64(@Result)^ := Value;
end;

そして、呼び出しコードは次のようになります。

C := 1.32;
if C < ShiftedInt64ToCurrency(13200) then
  Writeln ('strange');

コンパイラがそれを台無しにする方法はありません!

ふん!

于 2013-01-16T14:38:48.160 に答える
5

Currency(1.32) のようなハード キャストが不可能な場合は、明示的なキャストに次を使用できます。

Function ToCurrency(d:Double):Currency;
    begin
       Result := d;
    end;

procedure TForm1.Button1Click(Sender: TObject);

var
  C: Currency;

begin
  C := 1.32;
  if C < ToCurrency(1.32) then
  begin
    Writeln ('strange');
  end;
end;

別の方法として、const または variable を使用して通貨の使用を強制することもできます

const
  comp:Currency=1.32;
var
  C: Currency;
begin
  C := 1.32;
  if C < comp then
  begin
    writeln ('strange');
  end;
end;
于 2013-01-16T13:18:22.740 に答える
2

この問題 (コンパイラのバグ) を回避するには、@bummi の提案に従って実行するか、次のランタイム キャストを試してください。

if C < Currency(Variant(1.32)) then

FPU へのラウンドトリップ (および丸め誤差) を回避するには、次の比較関数の使用を検討してください。

function CompCurrency(const A,B: Currency): Int64;
var
  A64: Int64 absolute A; // Currency maps internally as an Int64
  B64: Int64 absolute B;
begin
  result := A64-B64;
end;
...
if CompCurrency(C,1.32) < 0 then
begin
  WriteLn('strange');
end;

詳細については、このページを参照してくださいFloating point and Currency fields

于 2013-01-16T16:13:37.387 に答える
0

デビッドの答えに追加するには、次のコードは奇妙ではありませんが、OP コードと同等です。

program Project2;

{$APPTYPE CONSOLE}

var
  I: Int64;
  E: Extended;

begin
  I:= 13200;
  E:= 13200;
  if I < E then
  begin
    WriteLn('strange');
  end;
  ReadLn;
end.

現在、コンパイラは Extended(13200) の正しいバイナリ値を生成するため、問題Currencyは Delphi コンパイラでの不適切な型の実装に関連しているようです。

于 2013-01-16T17:19:39.587 に答える