我知道关于浮点数的近似问题,所以我理解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.
Run Code Online (Sandbox Code Playgroud)
在x87中这段代码:
MyRoundValue2 := Round(MYVALUE2 * MyCalc);
Run Code Online (Sandbox Code Playgroud)
编译为:
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位精度的计算.因此浮点单元将最接近的64位值乘以5乘以0.9,即:
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.
Run Code Online (Sandbox Code Playgroud)
该程序产生与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.
Run Code Online (Sandbox Code Playgroud)