64位和32位之间的浮点差与Round

Gra*_*ter 9 delphi delphi-xe7

我知道关于浮点数的近似问题,所以我理解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)

Dav*_*nan 7

在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)

  • @LURD当然,因为你们都必须厌倦我说,Delphi RTL函数Set8087CW不是线程安全没有帮助.正如我已多次说过的那样,我告诉Emba如何解决这个问题,但他们不会这样做.也许是因为他们太害怕改变了. (2认同)