我在其中一个应用程序中遇到了Currency问题。我在Win32和Win64中获得了不同的结果。我在这里发现了一篇文章,显示了一个类似的问题,但是在XE6中修复了一个问题。我尝试做的第一件事是创建一个MCVE来复制问题。这就是轮子脱落的地方。与应用程序相比,MCVE中的相同代码看起来会产生不同的结果。生成的代码64位是不同的。所以我的问题变成了为什么它们不同,一旦我弄明白,那么我就可以创造一个合适的MCVE。
我有一个计算总数的方法。此方法调用另一种方法来获取需要添加到total的值。该方法返回一个。我将单个值分配给变量,然后将其添加到货币总额中。在我的主应用程序中,以后使用总计的值,但是将其添加到MCVE不会改变行为。我确保编译器选项是相同的。
在我的主应用程序中,计算结果是Win32中的$ 2469.6001和Win64中的2469.6,但我不能在MCVE中复制它。 “编译选项”页面上的所有内容都相同,并且禁用了优化。
这是我尝试过的MCVE的代码。这模仿了原始应用程序中的操作。
program Project4;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
TTestClass = class
strict private
FMyCurrency: Currency;
function GetTheValue: Single;
public
procedure Calculate;
property MyCurrency: Currency read FMyCurrency write FMyCurrency;
end;
procedure TTestClass.Calculate;
var
myValue: Single;
begin
FMyCurrency := 0.0;
myValue := GetTheValue;
FMyCurrency := FMyCurrency + myValue;
end;
function TTestClass.GetTheValue: Single;
var
myValueExact: Int32;
begin
myValueExact := 1159354778; // 2469.60009765625;
Result := PSingle(@myValueExact)^;
end;
var
testClass: TTestClass;
begin
testClass := TTestClass.Create;
try
testClass.Calculate;
WriteLn(CurrToStr(testClass.MyCurrency));
ReadLn;
finally
testClass.Free;
end;
end.
此代码为TTestClass.Calculate的最后两行生成以下汇编程序:
Project4.dpr.25: myValue := GetTheValue;
00000000004242A8 488B4D40 mov rcx,[rbp+$40]
00000000004242AC E83F000000 call TTestClass.GetTheValue
00000000004242B1 F30F11452C movss dword ptr [rbp+$2c],xmm0
Project4.dpr.26: FMyCurrency := FMyCurrency + myValue;
00000000004242B6 488B4540 mov rax,[rbp+$40]
00000000004242BA 488B4D40 mov rcx,[rbp+$40]
00000000004242BE F2480F2A4108 cvtsi2sd xmm0,qword ptr [rcx+$08]
00000000004242C4 F3480F5A4D2C cvtss2sd xmm1,qword ptr [rbp+$2c]
00000000004242CA F20F590D16000000 mulsd xmm1,qword ptr [rel $00000016]
00000000004242D2 F20F58C1 addsd xmm0,xmm1
00000000004242D6 F2480F2DC8 cvtsd2si rcx,xmm0
00000000004242DB 48894808 mov [rax+$08],rcx
这是主要应用程序的摘录。提供更多信息很困难,但我认为这不会改变问题的性质。在这个类中,FBulkTotal被声明为严格私有的Currency。 UpdateTotals是公开的。
procedure TMainApplicationClass.UpdateTotals(aMyObject: TMyObject);
var
bulkTotal: Single;
begin
..
bulkTotal := grouping.GetTotal(aMyObject, Self);
FBulkTotal := FBulkTotal + bulkTotal;
..
end;
这两行的生成代码是:
TheCodeUnit.pas.7357: bulkTotal := grouping.GetTotal(aMyObject, Self);
0000000006DB0804 488B4D68 mov rcx,[rbp+$68]
0000000006DB0808 488B9598000000 mov rdx,[rbp+$00000098]
0000000006DB080F 4C8B8590000000 mov r8,[rbp+$00000090]
0000000006DB0816 E8551C0100 call grouping.GetTotal
0000000006DB081B F30F114564 movss dword ptr [rbp+$64],xmm0
TheCodeUnit.pas.7358: FBulkTotal := FBulkTotal + bulkTotal;
0000000006DB0820 488B8590000000 mov rax,[rbp+$00000090]
0000000006DB0827 488B8D90000000 mov rcx,[rbp+$00000090]
0000000006DB082E F3480F2A8128010000 cvtsi2ss xmm0,qword ptr [rcx+$00000128]
0000000006DB0837 F30F104D64 movss xmm1,dword ptr [rbp+$64]
0000000006DB083C F30F590D54020000 mulss xmm1,dword ptr [rel $00000254]
0000000006DB0844 F30F58C1 addss xmm0,xmm1
0000000006DB0848 F3480F2DC8 cvtss2si rcx,xmm0
0000000006DB084D 48898828010000 mov [rax+$00000128],rcx
奇怪的是生成的代码是不同的。 MCVE有一个cvtsi2sd后跟一个cvtss2sd,但是当将单个值的内容复制到xmm1寄存器时,主应用程序使用movss代替cvtss2sd。我很确定这是导致不同结果的原因,但是无法创建MCVE,我甚至无法确认它是编译器的问题。
我的问题是什么可能导致代码生成中的这些差异?我认为优化可以做这种事情,但我确保它们是相同的。
在处理货币时,您不应该使用任何浮点类型值。
我建议您观看来自Computerphile的浮点数视频,其中他解释了计算机如何处理浮点值以及处理货币时不应使用它们的原因。 https://www.youtube.com/watch?v=PZRI1IfStY0