【问题标题】:Why is 64 bit Delphi app calculating different results than 32 bit build?为什么 64 位 Delphi 应用程序计算的结果与 32 位构建不同?
【发布时间】:2014-03-20 09:29:44
【问题描述】:

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

var
  currPercent, currGross, currCalcValue : Currency;
begin

  currGross := 1182.42;
  currPercent := 1.45;
  currCalcValue := (currGross * (currPercent * StrToCurr('.01')));
  ShowMessage(CurrToStr(currCalcValue));
end;

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

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

【问题讨论】:

  • 很可能是因为Extended 在 64 位中的精度损失。在 32 位中,Extended 是 10 个字节,但在 64 位中只有 8 个字节。 This is documented.
  • 如果您查看生成的机器代码,32 位和 64 位在您在第三行的计算过程中所做的事情非常不同。 32bit 使用浮点运算,64bit 使用整数运算。
  • @RemyLebeau 不,你的第一条评论离题太远了。即使在 8 字节精度模式下,32 位编译器也会给出与在 10 字节精度模式下相同的结果。请注意,正如您自己观察到的那样,在 64 位版本中不执行浮点计算。
  • 如果您查看 David 发布的代码,您会发现 32 位也使用整数运算,但整数运算在 FPU 中执行。货币毕竟不是浮点类型,它是整数。所以实际上,这些代码片段并没有做完全不同的事情。
  • @Rudy 您的分析不正确。 32 位代码执行浮点运算而不是整数运算。注意中间值 171,450.9。浮点单元执行浮点运算。我看到两个 fmulp 和一个 fdiv。这些是浮点算术运算。这两个版本正在执行非常不同的操作。中间值的截断是关键。

标签: delphi delphi-xe2 32bit-64bit


【解决方案1】:

这是基于您的代码的我的 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.

现在查看生成的代码。前 32 位:

Project3.dpr.13: currCalcValue := (currGross * (currPercent * StrToCurr('.01'))); 0041C409 8D45EC lea eax,[ebp-$14] 0041C40C BADCC44100 mov edx,$0041c4dc 0041C411 E8A6A2FEFF 呼叫@USrLAsg 0041C416 8B1504E74100 移动 edx,[$0041e704] 0041C41C 8B45EC 移动 eax,[ebp-$14] 0041C41F E870AFFFFF 调用 StrToCurr 0041C424 DF7DE0 拳头 qword ptr [ebp-$20] 0041C427 9B 等待 0041C428 DF2DD83E4200 字段 qword ptr [$00423ed8] 0041C42E DF6DE0 字段 qword ptr [ebp-$20] 0041C431 DEC9 fmulp st(1) 0041C433 DF2DE03E4200 字段 qword ptr [$00423ee0] 0041C439 DEC9 fmulp st(1) 0041C43B D835E4C44100 fdiv 双字指针 [$0041c4e4] 0041C441 DF3DE83E4200 拳头 qword ptr [$00423ee8] 0041C447 9B 等待

还有 64 位:

Project3.dpr.13: currCalcValue := (currGross * (currPercent * StrToCurr('.01'))); 0000000000428A0E 488D4D38 lea rcx,[rbp+$38] 0000000000428A12 488D1513010000 铅 rdx,[rel $00000113] 0000000000428A19 E84213FEFF 呼叫@USrLAsg 0000000000428A1E 488B4D38 mov rcx,[rbp+$38] 0000000000428A22 488B155F480000 mov rdx,[rel $0000485f] 0000000000428A29 E83280FFFF 调用 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

它会得到正确的答案。

【讨论】:

  • 您应该添加如何防止这种情况:currCalcValue := currGross * currPercent / 100; 将在两个平台上计算相同的结果。 * 将在 / 之前计算
  • @SirRufo 那只是一个贴膏药。 64 位编译器在每个中间步骤除以 10,000 的方式存在一个根本弱点。在带有额外乘法操作数的表达式上尝试您的解决方案。甚至只是不同的数字。
  • @SertacAkyuz 是的,我希望这就是它正在做的事情。他们如何尝试实现十进制类型似乎并不重要,每次都很糟糕。
  • @JeffCope 不。自 XE2 以来,x64 代码生成并没有真正改变。 XE5 中的行为相同。如果我使用十进制值,我会使用带有运算符重载的高级记录来滚动我自己的数据类型。我会先看看 .net 如何实现它的十进制类型。 Emba 的 Currency 类型很烂。
  • FWIW,对于有效的十进制类型,请尝试:rvelthuis.de/programs/decimals.html。我会尽快上传添加了 64 位代码的版本。
【解决方案2】:

在 XE5u2 和当前编写 XE6u1 时已修复此问题。

【讨论】:

    猜你喜欢
    • 2012-10-01
    • 2012-01-15
    • 1970-01-01
    • 2017-06-05
    • 2016-02-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多