为什么64位Delphi应用程序计算的结果与32位构建不同?

Jef*_*ope 15 delphi 32bit-64bit delphi-xe2

我们最近开始创建64位构建的应用程序.在比较测试期间,我们发现64位构建的计算方式不同.我有一个代码示例,演示了两个版本之间的差异.

var
  currPercent, currGross, currCalcValue : Currency;
begin

  currGross := 1182.42;
  currPercent := 1.45;
  currCalcValue := (currGross * (currPercent * StrToCurr('.01')));
  ShowMessage(CurrToStr(currCalcValue));
end;
Run Code Online (Sandbox Code Playgroud)

如果您在32位版本中逐步执行此操作,则使用17.1451计算currCalcValue,而使用17.145返回64位版本.

为什么64位构建不计算额外的小数位?所有变量都定义为4个十进制货币值.

Dav*_*nan 14

这是我的SSCCE基于您的代码.请注意控制台应用程序的使用.让生活更简单.

{$APPTYPE CONSOLE}

uses
  SysUtils;

var
  currPercent, currGross, currCalcValue : Currency;
begin
  currGross := 1182.42;
  currPercent := 1.45;
  currCalcValue := (currGross * (currPercent * StrToCurr('.01')));
  Writeln(CurrToStr(currCalcValue));
  Readln;
end.
Run Code Online (Sandbox Code Playgroud)

现在看一下生成的代码.前32位:

Project3.dpr.13: currCalcValue := (currGross * (currPercent * StrToCurr('.01')));
0041C409 8D45EC           lea eax,[ebp-$14]
0041C40C BADCC44100       mov edx,$0041c4dc
0041C411 E8A6A2FEFF       call @UStrLAsg
0041C416 8B1504E74100     mov edx,[$0041e704]
0041C41C 8B45EC           mov eax,[ebp-$14]
0041C41F E870AFFFFF       call StrToCurr
0041C424 DF7DE0           fistp qword ptr [ebp-$20]
0041C427 9B               wait 
0041C428 DF2DD83E4200     fild qword ptr [$00423ed8]
0041C42E DF6DE0           fild qword ptr [ebp-$20]
0041C431 DEC9             fmulp st(1)
0041C433 DF2DE03E4200     fild qword ptr [$00423ee0]
0041C439 DEC9             fmulp st(1)
0041C43B D835E4C44100     fdiv dword ptr [$0041c4e4]
0041C441 DF3DE83E4200     fistp qword ptr [$00423ee8]
0041C447 9B               wait 

和64位:

Project3.dpr.13: currCalcValue := (currGross * (currPercent * StrToCurr('.01')));
0000000000428A0E 488D4D38         lea rcx,[rbp+$38]
0000000000428A12 488D1513010000   lea rdx,[rel $00000113]
0000000000428A19 E84213FEFF       call @UStrLAsg
0000000000428A1E 488B4D38         mov rcx,[rbp+$38]
0000000000428A22 488B155F480000   mov rdx,[rel $0000485f]
0000000000428A29 E83280FFFF       call StrToCurr
0000000000428A2E 4889C1           mov rcx,rax
0000000000428A31 488B0510E80000   mov rax,[rel $0000e810]
0000000000428A38 48F7E9           imul rcx
0000000000428A3B C7C110270000     mov ecx,$00002710
0000000000428A41 48F7F9           idiv rcx
0000000000428A44 488BC8           mov rcx,rax
0000000000428A47 488B0502E80000   mov rax,[rel $0000e802]
0000000000428A4E 48F7E9           imul rcx
0000000000428A51 C7C110270000     mov ecx,$00002710
0000000000428A57 48F7F9           idiv rcx
0000000000428A5A 488905F7E70000   mov [rel $0000e7f7],rax

请注意,32位代码在FPU上执行算术,但64位代码使用整数运算执行它.这是关键的区别.

在32位代码中,执行以下计算:

  • 将'0.01'转换为货币,即100,允许固定点移动10,000.
  • 将14,500加载到FPU中.
  • 乘以100给出1,450,000.
  • 乘以11,824,200,得出17,145,090,000,000.
  • 除以10,000 ^ 2给出171,450.9.
  • 舍入到最接近的整数给出171,451.
  • 将其存储在您的货币变量中.因此结果是17.1451.

现在,在64位代码中,它有点不同.因为我们一直使用64位整数.它看起来像这样:

  • 将'0.01'转换为货币,即100.
  • 乘以14,500,即1,450,000.
  • 除以10,000即145.
  • 乘以11,824,200给1,714,509,000.
  • 除以10,000,即171,450.呃哦,这里精度不足.
  • 将其存储在您的货币变量中.因此结果是17.145.

所以问题是64位编译器在每个中间步骤中除以10,000.大概是为了避免溢出,更可能是64位整数而不是浮点寄存器.

是这样做的计算:

100 * 14,500 * 11,824,200 / 10,000 / 10,000
Run Code Online (Sandbox Code Playgroud)

它会得到正确的答案.

  • @SirRufo那只是贴膏药.64位编译器在每个中间步骤中除以10,000的方式存在根本性的弱点.在带有额外乘法操作数的表达式上尝试解决方案.甚至只是不同的数字. (4认同)
  • FWIW,对于有效的十进制类型,请尝试:http://rvelthuis.de/programs/decimals.html.我将上传即将添加64位代码的版本. (2认同)