我知道关于浮点数的近似问题,所以我理解4.5如果近似为4.4999999999999991,可以向下舍入到4。我的问题是为什么使用相同类型的32位和64位存在差异。
在下面的代码中,我有两个计算。在32位中,MyRoundValue1的值为4,MyRoundValue2的值为5.在64位中它们都是4.不应该结果与32位和64位一致吗?
{$APPTYPE CONSOLE}
const
MYVALUE1: Double = 4.5;
MYVALUE2: Double = 5;
MyCalc: Double = 0.9;
var
MyRoundValue1: Integer;
MyRoundValue2: Integer;
begin
MyRoundValue1 := Round(MYVALUE1);
MyRoundValue2 := Round(MYVALUE2 * MyCalc);
WriteLn(IntToStr(MyRoundValue1));
WriteLn(IntToStr(MyRoundValue2));
end.
答案 0 :(得分:7)
在x87中这段代码:
MyRoundValue2 := Round(MYVALUE2 * MyCalc);
编译为:
MyRoundValue2 := Round(MYVALUE2 * MyCalc); 0041C4B2 DD0508E64100 fld qword ptr [$0041e608] 0041C4B8 DC0D10E64100 fmul qword ptr [$0041e610] 0041C4BE E8097DFEFF call @ROUND 0041C4C3 A3C03E4200 mov [$00423ec0],eax
Delphi RTL下x87单元的默认控制字执行80位精度的计算。因此浮点单位将{乘以closest 64 bit value to 0.9乘以5,即:
0.90000 00000 00000 02220 44604 92503 13080 84726 33361 81640 625
请注意,此值大于0.9。事实证明,当乘以5并四舍五入到最接近的80位值时,该值大于4.5。因此Round(MYVALUE2 * MyCalc)
返回5.
在64位时,浮点数学运算在SSE单元上完成。这不使用80位中间值。事实证明,最接近0.9倍的5倍,舍入到双精度正好是4.5。因此Round(MYVALUE2 * MyCalc)
在64位上返回4。
通过存储为double而不是依赖于中间80位值,您可以说服32位编译器的行为与64位编译器相同:
{$APPTYPE CONSOLE}
const
MYVALUE1: Double = 4.5;
MYVALUE2: Double = 5;
MyCalc: Double = 0.9;
var
MyRoundValue1: Integer;
MyRoundValue2: Integer;
d: Double;
begin
MyRoundValue1 := Round(MYVALUE1);
d := MYVALUE2 * MyCalc;
MyRoundValue2 := Round(d);
WriteLn(MyRoundValue1);
WriteLn(MyRoundValue2);
end.
该程序产生与64位程序相同的输出。
或者您可以强制x87单元使用64位中间体。
{$APPTYPE CONSOLE}
uses
SysUtils;
const
MYVALUE1: Double = 4.5;
MYVALUE2: Double = 5;
MyCalc: Double = 0.9;
var
MyRoundValue1: Integer;
MyRoundValue2: Integer;
begin
Set8087CW($1232); // <-- round intermediates to 64 bit
MyRoundValue1 := Round(MYVALUE1);
MyRoundValue2 := Round(MYVALUE2 * MyCalc);
WriteLn(MyRoundValue1);
WriteLn(MyRoundValue2);
end.
答案 1 :(得分:3)
System.Round 在内部接受扩展值。在32位计算中,FPU中的扩展。在64位扩展中类似于 Double 。内部表示可能差别很大,以产生差异。