9.5. Использование математического сопроцессора 80X87

We use cookies. Read the Privacy and Cookie Policy

Применение сопроцессора значительно увеличивает точность математических расчетов и ускоряет их выполнение. Дело лишь в малом — наличии его и умении использовать.

- 181 -

Чтобы программа могла задействовать возможности сопроцессора, она должна в своем начале иметь директиву (ключ режима компилятора) $N+ :

{$N+ программа для 80X87}

PROGRAM Name;

USES

...

Режим компиляции $N+ является глобальным и не может меняться в дальнейшем. При создании модулей (UNIT), ориентированных на работу с сопроцессором, т.е. использующих вводимые им типы, указание ключа $N+ в них необязательно. Важно лишь, чтобы он был в главной программе, включающей в себя эти модули.

При необходимости отключить сопроцессор должен указываться ключ $N-. При этом программа может перестать компилироваться (компилятор «забудет» типы чисел с повышенной точностью).

Если в ПЭВМ установлен сопроцессор, то компилятор сам определяет ключевое слово CPU87 для условной компиляции. Это можно использовать для автоматического выбора режима компиляции:

{$IFDEF CPU87} {$N+} {$ELSE} {$N-} {$ENDIF}

Приведенная выше конструкция определяет, как будет скомпилирован текст — в расчете на сопроцессор или без него.

После компиляции выполнение программы всякий раз начинается с проверки наличия сопроцессора и определения его типа. Результат проверки записывается в предопределенную переменную системной библиотеки — Test8087 типа Byte (табл. 9.8).

Таблица 9.8

Значения Test8087

Расшифровка

0

Сопроцессор не обнаружен

1

Подключен 8087

2

Подключен 80287

3

Подключен 80387

Если программа компилировалась в режиме {$N+}, а значение Test8087 получилось равным 0, то программа остановится с выдачей сообщения о необходимости сопроцессора. Существует, однако, средство отключить автоматическую проверку наличия сопроцессора при запуске программы. Надо ввести системную переменную MS-DOS с именем 87 и значениями Y (от YES — да) и N (от NO — нет). Лучше всего это сделать в файле AUTOEXEC.BAT, вставив строку

- 182 -

SET 87=Y (или N)

Значение системной переменной MS-DOS 87, равное Y, прикажет считать сопроцессор подключенным, а N — соответственно отключенным. Вообще говоря, лучше не обманывать технику и программы. Приберегите эту методику (с SET 87=) на самый крайний случай.

Турбо Паскаль дает возможность эмулировать работу сопроцессора программным путем. Это означает, что можно создать программу, которая будет работать с высокой точностью независимо от наличия сопроцессора. Обнаружен сопроцессор — хорошо, он и будет нагружен, нет сопроцессора — вся точность будет получена имитацией его. Понятно, что в последнем случае будут потери во времени счета, и немалые. Включением эмуляции управляет ключ $Е. Он имеет смысл только рядом с ключом $N. Возможны такие их сочетания:

{$N+, E+} — подключение библиотеки для эмуляции сопроцессора; при его отсутствии точность обеспечивается программно за счет скорости; {$N+, E-} — программа сможет работать только на машинах с сопроцессором;

{$N-,E+} и {$N-,E-} — сопроцессор не используется, ключ эмуляции игнорируется. Программа работает только с обычной точностью и скоростью.

Если в программу вставлен внешний код директивой {$L Имя-Файла.OBJ}, то для работы с сопроцессором этот код должен быть получен с учетом использования инструкций 80X87.

Преимущества от использования сопроцессора — это, в первую очередь, скорость вычислений, которая может вырасти в несколько раз. Второе преимущество — увеличение точности вычислений с плавающей точкой. В расчете на математический сопроцессор вводятся типы, приведенные в табл. 9.9.

Таблица 9.9

Тип

Диапазон значений

Количество значащих цифр

Размер в байтах

Single

1.5E-45..3.4e+38

7-8

4

Double

5.0E-324..1.7E+308

15-16

8

Extended

3.4E-4932..1.1E+4932

19-20

10

Comp

-9.2E+18..9.2E+18

19-20

8

Все эти типы — вещественные, за исключением Comp, который является «очень длинным» целым типом (хранит только целые

- 183 -

значения). Диапазон этого типа в таблице задан округленно, так как реальные числа (от -2 до 263-1) слишком длинны.

Обычный тип Real (6 байт, диапазон 2.9Е-39...1.7Е+38, 11-12 значащих цифр) будет работать с сопроцессором, но крайне неэффективно. Этот формат — чужой для сопроцессора, и время, «съедаемое» преобразованием его в сопроцессорный тип, перекрывает ускорение. А точности не добавляется. Поэтому лучше всего ввести свой тип, например Float, и понимать под ним либо Real, либо чисто сопроцессорный вещественный тип в зависимости от режима компиляции.

{$N... <-- какой-либо режим }

{$IFOPT N+}

TYPE

Float = Double; { или любой другой тип для 80X87 }

{$ELSE>

TYPE

Float = Real; { без 80X87 — только этот тип }

{SENDIF}

VAR { переменные типа Float }

r : Float;

d : Array [1..9] of Float;

Целые типы Турбо Паскаля работают с сопроцессором без каких-либо оговорок.

Особо важным является вопрос точности вычислений. При использовании сопроцессора все стандартные математические операторы и функции языка, возвращающие обычно значения Real, начинают возвращать значения типа Extended. В связи с этим имеет смысл опираться именно на этот тип как базовый. Тем не менее вполне возможно, что в программе будут участвовать переменные разных типов. В таких случаях при необходимости будет производиться преобразование значений, а значит, потеря точности. При вычислении значений правых частей операторов присваивания результат имеет точность, совпадающую с наиболее точным из типов членов выражения (или, что то же самое, с наиболее емким типом). Это означает, что в присваивании

VAR

e1, e2 : Extended;

e3 : Double;

result : Single;

...

result := e1*e2/e3;

- 184 -

значение выражения справа будет вычислено как тип Extended. Но при присваивании его переменной result «малого» типа Single будет произведено усечение, и резко уменьшится число значащих цифр после десятичной точки. Подобные ситуации надо предвидеть и стараться избегать их. Особенно неприятны они в циклах суммирования:

VAR

е : Extended:

Sum : Single;

i : Word;

...

BEGIN

e:=1.23456e-12;

Sum:=0;

for i:=32767 to 65535 do Sum := Sum + i/e;

...

END.

Здесь подобные потери будут повторены тысячи раз, и накопленная ошибка может быть соизмерима с самой суммой. Исправить ситуацию легко: надо ввести дополнительную переменную eSum точного типа Extended для сумматора, и переписать цикл:

eSum:=0;

for i:=32767 to 65535 do eSum := eSum + i/e;

Sum:=eSum;

Теперь потери будут значительно меньше.

По той же причине (из-за усечения точности) некорректной является операция сравнения двух разнотипных вещественных переменных или переменной с выражением (последнее, как уже отмечалось, может быть вычислено в типе Extended). Так, сравнение в примере:

VAR

e : Extended;

d : Double;

...

e := Cos( Pi/8 );

d := e;

{==>} if d=e then ...

при формальной правильности и очевидности даст результат False — ложно, так как d имеет меньше значащих цифр, чем e. Обычно при сравнении вещественных значений проверяют не их совпадение, а степень расхождения. Если эта степень соизмерима с точностью представления наиболее грубого числа, то значения можно считать

- 185 -

равными. Так, условие if в последнем примере следовало бы переписать так:

if Abs(d-e) < 1.0Е-15 then ...

Здесь 1.0Е-15 — точность для типа переменной d (Double).

Продолжим перечень особенностей применения сопроцессора. Его наличие в ПЭВМ и использование сильно влияет на работу функции округления Round: она начинает округлять полуторные значения в сторону ближайшего четного целого числа (это называется «банковским способом»)! Например:

без сопроцессора с сопроцессором

Round(0.5) --> 1 Round(0.5) --> 0

Round(1.5) --> 2 Round(1.5) --> 2

Round(2.5) --> 3 Round(2.5) --> 2

Round(3.5) --> 4 Round(3.5) --> 4

С остальными значениями (без '.5') функция работает нормально.

Некоторые неприятности могут поджидать любителей рекурсивного подхода к написанию функции. Возможны, в принципе, ситуации, когда рекурсивные вызовы переполнят внутренний стек данных сопроцессора, рассчитанный на восемь уровней рекурсии, и возникнет сбой программы. Возможным решением будет разнесение сложнорекурсивных выражений типа Fn:=Fn(N-1)+Fn(N-2) по локальным переменным, например, f1:=Fn(N-1); и f2:=Fn(N-2). После этого выражение Fn:=f1+f2 будет безопасным для сопроцессора.

Завершая тему использования сопроцессора, напомним, что и расширенные вещественные типы, и тип Real при работе с сопроцессором 80X87 выводятся на печать операторами Write и WriteLn с 4 цифрами в показателе степени:

при $N- WriteLn(123.4) выдаст 1.2340000000Е+02,

но при $N+ WriteLn(123.4) выдаст 1.234000000000000Е+0002.

Этот факт надо учитывать при форматированном выводе и при преобразовании чисел в строку процедурой Str.

- 186 -