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
.
3.2.12. Машинное эпсилон
Когда мы имеем дело с вычислениями с ограниченной точностью, возникает такой парадокс. Пусть, например, мы считаем с точностью до трех значащих цифр. Прибавим к числу 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.
Листинг 3.16 показывает, как можно найти это число (пример Epsilon на компакт-диске).
Листинг 3.16. Поиск машинного эпсилон
procedure TForm1.Button1Click(Sender: TObject);
var
R: Extended;
I: Integer;
begin
R := 1;
while 1 + R/2 > 1 do R := R / 2;
Label1.Caption := FloatToStr(R);
end;
Запустив этот код, мы получим на экране 1.0842021724855Е-19 в полном соответствии с нашими теоретическими выкладками.
Примечание
В тех системах, где наблюдается описанная проблема с уменьшением точности, программа выдаст 2.22044604925031Е-16. Если вы увидели у себя это число, добавьте код, который переведет FPU в режим максимальной точности.
А теперь изменим тип переменной
R
с
Extended
на
Double
. Результат не изменится. На
Single
— опять не изменится. Но такое поведение лишь на первый взгляд может показаться странным. Давайте подробнее рассмотрим выражение
1 + R / 2 > 1
. Итак, все вычисления (в том числе и сравнение) сопроцессор выполняет с данными типа
Extended
. Последовательность действий такова: число
R
загружается в регистр сопроцессора, преобразуясь при этом к типу
Extended
. Дальше оно делится на 2, а затем к результату прибавляется 1, и все это в
Extended
, никакого обратного преобразования в Single или
Double
не происходит. Затем это число сравнивается с единицей. Очевидно, что результат сравнения не должен зависеть от исходного типа
R
, т.к. диапазона даже типа
Single
вполне хватает, чтобы разместить машинное эпсилон.
3.2.13. Методы решения проблем
Подведем итоги сказанному. Значения, которые мы получаем, могут отличаться от ожидаемых, даже если речь идет о простом присваивании. Во многих случаях (например, в научных расчетах) это несущественно, т.к. сам метод расчета дает еще большую погрешность. Проблемы начинаются там, где мы хотим вывести число на экран или сравнить его с другим. Универсальных рецептов на все случаи жизни не существует, но во многих ситуациях помогают следующие советы:
□ Если ваша задача — просто получить "красивое" представление числа на экране, то функцию
FloatToStr
заменяйте на ее более мощный аналог
FloatToStrF
или на функцию
Format
— они позволяют указать желаемое количество символов после точки.
□ Сравнение вещественных чисел следует выполнять с учетом погрешности, т.е. вместо
if а = b
… писать
if Abs(а - b) < Ерs
…, где
Eps
— некоторая величина, задающая допустимую погрешность (в модуле Math, начиная с Delphi 6, существует функция
SameValue
, с помощью которой это же условие можно записать как