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).
Листинг 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".
В модуле Windows
особым образом импортируются функции CreateWindow
и CreateWindowEx
, которые, видимо, тоже были замечены в некорректном обращении с управляющим словом FPU. Вот как, например, выглядит импорт функции CreateWindowEx
(листинг 3.15).
Листинг 3.15. Импорт функции CreateWindowEx
модулем Windows
function _CreateWindowEx(dwExStyle: WORD; lpClassName: PChar; lpWindowName: PChar; dwStyle: DWORD; X, Y, nWidth, nHeight: Integer; hWndParent: HWND; hMenu: HMENU; hInstance: HINST; lpParam: Pointer): HWND; stdcall; external user32 name 'CreateWindowExA';
function CreateWindowEx(dwExStyle: DWORD; lpClassName: PChar; lpWindowName: PChar; dwStyle: DWORD; X, Y, nWidth, nHeight: Integer; hWndParent: HWND; hMenu: HMENU; hInstance: HINST; lpParam: Pointer): HWND;
var
FPUCW: Word;
begin
FPUCW := Get8087CW;
Result :=
_CreateWindowEx(dwExStyle, lpClassName, lpWindowName,
dwStyle, X, Y, nWidth, nHeight, hWndParent, hMenu,
hInstance, lpParam);
Set8087CW(FPUCW);
end;
Модуль Windows
импортирует функцию CreateWindowExA
из библиотеки user32.dll, но дает ей измененное название и не показывает ее в своем интерфейсе. Вместо этого он экспортирует другую функцию с названием CreateWindowEx
(и аналогичную с названием CreateWindowExA
), которая является оберткой над настоящей CreateWindowExA
и обеспечивает сохранение значения управляющего слова FPU. Аналогичным способом импортируется и Unicode-вариант функции. Таким образом, стандартные библиотеки обеспечивают вызов безопасного варианта CreateWindowEx
в любой программе.
Примечание
В модуле Windows
можно обнаружить еще одну интересную деталь: функции CreateWindowA
и CreateWindowW
из библиотеки user32.dll этим модулем вообще не импортируются. Вместо этого одноименные обертки вызывают импортированные функции _CreateWindowExA
и _CreateWindowExW
, передавая им 0 в качестве значения параметра dwExStyle
.
Когда мы имеем дело с вычислениями с ограниченной точностью, возникает такой парадокс. Пусть, например, мы считаем с точностью до трех значащих цифр. Прибавим к числу 1,00 число 1,00·10 -4. Если бы все было честно, мы получили бы 1,0001. Но у нас ограничена точность, поэтому мы вынуждены округлять до трех значащих цифр. В результате получается 1,00. Другими словами, к некоторому числу мы прибавляем другое число, большее нуля, а в результате из-за ограниченной точности мы получаем то же самое число. Наименьшее положительное число, которое при добавлении его к единице дает результат, не равный единице, называется машинным эпсилон .
Понятие машинного эпсилон у новичков нередко путается с понятием наименьшего числа, которое может быть записано в выбранном формате. Это неправильно. Машинное эпсилон определяется только размером мантиссы, а минимально возможное число оказывается существенно меньше из-за сдвига плавающей двоичной точки с помощью экспоненты.
Прежде чем искать машинное эпсилон программно, попытаемся найти его из теоретических соображений. Итак, мантисса типа Extended
содержит 64 разряда. Чтобы закодировать единицу, старший бит мантиссы должен быть равен 1 (денормализованная запись), остальные биты — нулю. Очевидно, что при такой записи наименьшее из чисел, для которых выполняется условие x > 1, получается, когда самый младший бит мантиссы тоже будет равен единице, т.е. х = 1,00...001 (в двоичном представлении, между точкой и младшей единицей 62 нуля). Таким образом, машинное эпсилон равно х -1, т.е. 0.00...001. В более привычной десятичной форме записи это будет 2 -63, т.е. примерно 1,084·10 -19.
Читать дальше
Конец ознакомительного отрывка
Купить книгу