货币价值/常数价值比较的奇怪结果

mjn*_*mjn 18 delphi compare delphi-2009

当使用Delphi 2009编译并运行时,此控制台应用程序会写"奇怪"."小于"运算符两边的值相等,但代码的行为就好像它们不相等.我该怎么做才能避免这个问题?

program Project5;

{$APPTYPE CONSOLE}

var
  C: Currency;
begin
  C := 1.32;

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

  ReadLn;
end.
Run Code Online (Sandbox Code Playgroud)

ps代码适用于其他值.

答案由巴里·凯利解释说,货币型"是不容易的浮点代码是相同的方式精度的问题."

Dav*_*nan 9

这似乎是德尔福的回归.

Delphi 2010中的输出是"奇怪的".但是在XE2中没有输出,因此错误不存在.我手头没有XE可以测试,但感谢@Sertac确认XE也输出'奇怪'.请注意,旧版本的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');
Run Code Online (Sandbox Code Playgroud)

文字1.32存储为10字节浮点值,其值应为13200.这是一个可精确表示的二进制浮点值.存储为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');
Run Code Online (Sandbox Code Playgroud)

请注意,文字保存在4字节的浮点数中.我们可以通过比较来看出这一点dword ptr [$00406128].如果我们查看存储在$00406128我们发现的单精度浮点数的内容:

00 40 4E 46

这正好是13200,表示为4字节的浮点数.

我的猜测是2010年的编译器遇到以下情况时会做以下事情1.32:

  • 将1.32转换为最接近的10字节浮点数.
  • 将该值乘以10000.
  • 将生成的10字节浮点数存储在$00405118.

因为1.32不能完全表示,所以最终的10字节浮点数并不完全是13200.并且可能是当编译器从4字节浮点数中存储这些文字到将它们存储在10字节浮点数时进行回归.

根本问题在于Delphi对Currency数据类型的支持建立在一个完全有缺陷的设计上.使用二进制浮点算法实现十进制定点数据类型只是在寻找麻烦.修复设计的唯一理智方法是完全重新设计编译器以使用定点整数算法.值得注意的是,新的64位编译器使用与32位编译器相同的设计.

老实说,我会阻止Delphi编译器使用Currency文字进行任何浮点运算.这只是一个完整的雷区.我会像这样在我脑海里做10000次转变:

function ShiftedInt64ToCurrency(Value: Int64): Currency;
begin
  PInt64(@Result)^ := Value;
end;
Run Code Online (Sandbox Code Playgroud)

然后调用代码将是:

C := 1.32;
if C < ShiftedInt64ToCurrency(13200) then
  Writeln ('strange');
Run Code Online (Sandbox Code Playgroud)

编译器无法搞砸了!

哼!

  • XE很奇怪*. (6认同)

bum*_*mmi 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;
Run Code Online (Sandbox Code Playgroud)

另一种方法可以通过使用const或变量来强制使用curreny

const
  comp:Currency=1.32;
var
  C: Currency;
begin
  C := 1.32;
  if C < comp then
  begin
    writeln ('strange');
  end;
end;
Run Code Online (Sandbox Code Playgroud)