Различия с плавающей запятой между 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.