Различия с плавающей запятой между 64 бит и 32 бит с круглым

Я знаю все о проблемах аппроксимации с числами с плавающей запятой, поэтому я понимаю, как 4.5 может округляться до 4, если он был аппроксимирован как 4.499999999999999991. Мой вопрос в том, почему есть разница, используя те же типы с 32 бит и 64 бит.

В приведенном ниже коде у меня есть два расчета. В 32 бит значение для MyRoundValue1 равно 4, а значение для MyRoundValue2 равно 5. В 64 бит они оба 4. Не должны Результаты быть совместимы как с 32 bit и 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.

2 ответов


в x87 этот код:

MyRoundValue2 := Round(MYVALUE2 * MyCalc);

составляется:

MyRoundValue2 := Round(MYVALUE2 * MyCalc);
0041C4B2 DD0508E64100     fld qword ptr [41e608]
0041C4B8 DC0D10E64100     fmul qword ptr [41e610]
0041C4BE E8097DFEFF       call @ROUND
0041C4C3 A3C03E4200       mov [423ec0],eax

управляющее слово по умолчанию для блока x87 в Delphi RTL выполняет вычисления с точностью до 80 бит. Таким образом, единица с плавающей запятой умножает 5 на ближайшее 64-битное значение до 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-битные промежуточные значения. И получается, что 5 раз ближайший двойник до 0,9, округленный до двойной точности, составляет ровно 4,5. Отсюда Round(MYVALUE2 * MyCalc) возвращает 4 на 64 бит.

вы можете убедить 32-битный компилятор вести себя так же, как 64-битный компилятор, сохраняя двойной, а не полагаясь на промежуточные 80-битные значения:

{$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(32); //  <-- round intermediates to 64 bit
  MyRoundValue1 := Round(MYVALUE1);
  MyRoundValue2 := Round(MYVALUE2 * MyCalc);
  WriteLn(MyRoundValue1);
  WriteLn(MyRoundValue2);
end.