Раз уж мы заговорили об управляющем слове, давайте немного поэкспериментируем с ним. Изменим первую строчку на Set8087CW(Get8087CW and $FCFF or $0200)
. Тем самым мы перевезем сопроцессор в режим 53-разрядной точности представления мантиссы. Теперь в любой системе мы увидим 1.44327637948555Е-16, несмотря на использование Extended
. Если же мы изменим первую строчку на Set8087CW(Get8087CW and $FCFF)
, то будем работать в режиме 24-разрядной точности. Соответственно, в любой системе будет результат -7.3015691270939Е-8.
Заметим, что при загрузке в 10-байтный регистр сопроцессора числа типа Extended
в режиме пониженной точности "лишние" биты не обнуляются. Только результаты математических операций представляются с пониженной точностью. Кроме того, при сравнении двух чисел также учитываются все биты, независимо от точности. Поэтому код, приведенный в листинге 3.10 при выборе любой точности даст Не равно.
В том, что описанная проблема с потерей точности встречается все реже, есть заслуга и разработчиков VCL. Зная, вызовы каких функций могут привести к изменению управляющего слова FPU, они перед этими вызовами запоминают управляющее слово, а затем восстанавливают. В более поздних версиях Delphi количество таких "оберток" больше, чем в ранних, поэтому чем новее версия Delphi, тем меньше шанс столкнуться с описанной проблемой. Здесь мы рассмотрим несколько примеров из исходного кода стандартных модулей Delphi 2007.
Для динамической загрузки DLL предназначена API-функция LoadLibrary
. В модуле SysUtils
для этой функции предлагается обертка, называющаяся SafeLoadLibrary
(листинг 3.13).
SysUtils.SafeLoadLibrary
{ SafeLoadLibrary calls LoadLibrary, disabling normal Win32 error message popup dialogs if the requested file can't be loaded. SafeLoadLibrary also preserves the current FPU control word (precision, exception masks) across the LoadLibrary call (in case the DLL you're loading hammers the FPU control word in its initialization, as many MS DLLs do) }
function SafeLoadLibrary(const Filename: string; ErrorMode: UINT): HMODULE;
var
OldMode: UINT;
FPUControlWord: Word;
begin
OldMode:= SetErrorMode(ErrorMode);
try
asm
FNSTCW FPUControlWord
end;
try
Result:= LoadLibrary(PChar(Filename));
finally
asm
FNCLEX
FLDCW FPUControlWord
end;
end;
finally
SetErrorMode(OldMode);
end;
end;
Как видно из комментария, проблема в том, что многие системные библиотеки изменяют управляющее слово FPU при своей инициализации.
В функции CreateADOObject
(внутренняя функция модуля ADODB
) тоже сохраняется и восстанавливается управляющее слово (листинг 3.14).
CreateADOObject
модуля ADODB
function CreateADOObject(const ClassID: TGUID): IUnknown;
var
Status: HResult;
FPUControlWord: Word;
begin
asm
FNSTCW FPUControlWord
end;
Status:=
CoCreateInstance(ClassID, nil, CLSTX_INPROC_SERVER or CLSCTX_LOCAL_SERVER, IUnknown, Result);
asm
FNCLEX
FLDCW FPUControlWord
end;
if (Status = REGDB_E_CLASSNOTREG) then
raise Exception.CreateRes(@SADOCreateError)
else OleCheck(Status);
end;
Здесь восстанавливать управляющее слово приходится после вызова системной функции CoCreateInstance
, создающей СОМ-объект. Но, судя по тому, что больше нигде при вызове CoCreateInstance
такой код не используется, проблема не в самой функции, а в тех конкретных ADO-объектах, которые создаются здесь с ее помощью.
Аналогичную защиту можно обнаружить в модуле Dialogs
, в методе TCommonDialog.TaskModalDialog
. Комментарий к этой защите гласит: "Avoid FPU control word change in NETRAP.dll, NETAPI32.dll, etc".