Why is 64 bit Delphi app calculating different results than 32 bit build?

前端 未结 2 1646
太阳男子
太阳男子 2021-02-19 03:31

We recently started the process of creating 64 bit builds of our applications. During comparison testing we found that the 64 bit build is calculating differently. I have a code

相关标签:
2条回答
  • 2021-02-19 03:36

    This has been fixed as of XE5u2 and as of current writing XE6u1.

    0 讨论(0)
  • 2021-02-19 03:51

    Here's my SSCCE based on your code. Note the use of a console application. Makes life much simpler.

    {$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.
    

    Now look at the code that is generated. First 32 bit:

    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 
    

    And the 64 bit:

    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
    

    Note that the 32 bit code performs the arithmetic on the FPU, but the 64 bit code performs it using integer arithmetic. That's the key difference.

    In the 32 bit code, the following calculation is performed:

    • Convert '0.01' to currency, which is 100, allowing for the fixed point shift of 10,000.
    • Load 14,500 into the FPU.
    • Multiply by 100 giving 1,450,000.
    • Multiply by 11,824,200 giving 17,145,090,000,000.
    • Divide by 10,000^2 giving 171,450.9.
    • Round to the nearest integer giving 171,451.
    • Store that in your currency variable. Hence the result is 17.1451.

    Now, in the 64 bit code, it's a little different. Because we use 64 bit integers all the way. It looks like this:

    • Convert '0.01' to currency, which is 100.
    • Multiply by 14,500 which is 1,450,000.
    • Divide by 10,000 which is 145.
    • Multiply by 11,824,200 giving 1,714,509,000.
    • Divide by 10,000 which is 171,450. Uh-oh, loss of precision here.
    • Store that in your currency variable. Hence the result is 17.145.

    So the issue is that the 64 bit compiler divides by 10,000 at each intermediate step. Presumably to avoid overflow, much more likely in a 64 bit integer than a floating point register.

    Were it to do the calculation like this:

    100 * 14,500 * 11,824,200 / 10,000 / 10,000
    

    it would get the right answer.

    0 讨论(0)
提交回复
热议问题