Глава 15. Использование сопроцессора 80x87

             В Borland Pascal вы можете работать с двумя типами  чисел  -

        целыми (короткими целыми - Shortint,  целыми - Integer,  длинными

        целыми - Longint,  целыми длиной в байт - Byte,  целыми длиной  в

        слово - Word) и вещественными (вещественными - Real, вещественны-

        ми одинарной точности - Single,  вещественными двойной точности -

        Double, повышенной точности - Extended, сложными - Comp). Вещест-

        венные числа называют также числами с плавающей точкой (плавающей

        запятой). Для облегчения работы с целыми числами создан процессор

        8086,  но для работы с вещественными числами на  этом  процессоре

        затрачивается гораздо больше времени и усилий. Для семейства про-

        цессоров 8086 предназначено соответствующее  семейство  вспомога-

        тельных  специализированных процессоров для математических вычис-

        лений (сопроцессоров) 80x87.

 

             Процессор 80x87 - это специальный сопроцессор для  обработки

        чисел, который может входить в состав вашего компьютера РС. С по-

        мощью него операции с плавающей точкой выполняются очень  быстро.

        Поэтому если вы собираетесь использовать большой объем вычислений

        с плавающей точкой, то вам, вероятно, понадобится сопроцессор.

 

             Borland Pascal построен таким образом,  что он  обеспечивает

        оптимальное  выполнение операций с плавающей точкой независимо от

        наличия сопроцессора 80x87.

 

             * Для программ,  работающих на компьютере РС,  независимо от

               того,  оснащен  он сопроцессором 80x87 или нет,  в Borland

               Pascal предусмотрено использование  вещественных  чисел  и

               соответствующая библиотека программ, которые предназначены

               для выполнения операций с плавающей точкой.  Числа вещест-

               венного типа занимают 6 байт памяти.  При этом обеспечива-

               ется представление чисел  в  диапазоне  от  2.9х10^-39  до

               1.7х10^38 с 11-12 значащими цифрами. Программы в библиоте-

               ке программ для работы с плавающей  точкой  оптимизированы

               по  скорости  и  по  размеру  и  используют самые новейшие

               средства процессора 80x87.

 

             * Если вы пишете программы,  использующиеся только на компь-

               ютерах,  оснащенных сопроцессором 80x87, то вы можете ука-

               зать Borland Pascal на необходимость получения выполняемо-

               го  кода,  в  котором используется плата процессора 80x87.

               Это даст вам возможность  использования  четырех  дополни-

               тельных типов вещественных чисел (одинарной и двойной точ-

               ности,  повышенной точности,  сложного типа) и расширенный

               диапазон  представления  чисел  с  плавающей  точкой  - от

               1.9х10^-4951 до 1,1х10^4943 с 19-20 значащими цифрами.

 

             С помощью  директивы  компилятора  $N  или  параметра   меню

        OptionsіCоmpiler  (ПараметрыіКомпилятор) 80x87/80287 можно перек-

        лючаться между различными моделями  генерации  кода  с  плавающей

        точкой. По умолчанию используется состояние {$N-}. В этом состоя-

        нии компилятор использует 6-байтовую библиотеку с плавающей  точ-

        кой, что позволяет вам работать только с переменными типа Real. В

        состоянии {$N+} компилятор генерирует код для сопроцессора 80x87,

        что  дает вам дополнительную точность и доступ к 4 дополнительным

        вещественным типам.

 

             В Windows при компиляции с режимом  числовой  обработки,  то

        есть  с  директивой {$N+},  убедитесь,  что в вашей системе можно

        найти библиотеку эмуляции Windows 8087 - WIN87EM.DLL. Эта библио-

        тека   обеспечивает  необходимый  интерфейс  между  сопроцессором

        80х87,  Windows и вашей прикладной программой.  Если  сопроцессор

        80х87 в вашей системе отсутствует,  то библиотека WIN87EM.DLL бу-

        дет эмулировать его программно.  Эмуляция  существенно  замедляет

        работу по сравнению с реальным сопроцессором 80х87,  но обеспечи-

        вает выполнение вашей прикладной программы на любой машине.

 

             В реальном или защищенном режиме DOS,  даже если у  вас  нет

        сопроцессора  8087,  вы можете указать Borland Pascal,  что нужно

        включить библиотеку исполняющей системы,  которая эмулирует ариф-

        метический  сопроцессор 8087.  В случае наличия сопроцессора 8087

        он используется.  Если сопроцессор отсутствует, его работа эмули-

        руется  библиотекой исполняющей системы (за счет некоторой потери

        скорости работы программы).

 

             Для разрешения и запрещения эмуляции сопроцессора  8087  ис-

        пользуются директива компилятора $E и параметр Emulation  (Эмуля-

        ция)  меню OptionsіCompiler (ПараметрыіКомпилятор).  По умолчанию

        используется состояние {$E+}.  В этом состоянии в программу авто-

        матически включается полная эмуляция сопроцессора 8087. В состоя-

        нии {$E-} используется существенно  меньшая  часть  библиотеки  с

        плавающей точкой, а полученный в результате файл .EXE будет рабо-

        тать только на машинах с сопроцессором 8087.

 

             В приложении  Windows директива компилятора $E не действует.

        Не действует она также в модуле.  Более того, если программа ком-

        пилировалась с директивой {$N-},  а все модули программы компили-

        ровались с директивой {$N+},  то библиотека  исполняющей  системы

        для  сопроцессора  8087 не требуется,  и директива компилятора $E

        игнорируется.

 

             Прикладной программе Windows не требуется библиотека  испол-

        няющей системы 80x87. Вместо этого ей нужно поддерживающая библи-

        отека WIN87EM.DLL,  поставляемая с Windows,  которая обеспечивает

        необходимый интерфейс между вашей прикладной программой,  Windows

        и сопроцессором.  Таким образом, в Windows даже при наличии в ва-

        шей системе сопроцессора 80х87 для выполнения программ, скомпили-

        рованных в состоянии {$N+}, должна присутствовать библиотека эму-

        ляции WIN87EM.DLL  (данная  библиотека - это часть Windows,  а не

        Borland Pascal).  При отсутствии сопроцессора  WIN87EM.DLL  будет

        эмулировать его операции программным путем,  что замедляет выпол-

        нение программы и не гарантирует,  что  использующая  сопроцессор

        80x87 программа сможет работать на любой машине.

             Когда вы запускаете прикладную программу Windows,  cкомпили-

        рованную в состоянии {$N+}, убедитесь, что она может найти в сис-

        теме файл WIN87EM.DLL.

 

             Когда вы выполняете компиляцию в режиме кода 80х87 (директи-

        ва {$N+}),  то возвращаемые подпрограммы модуля Systем (Sqrt, Рi,

        Sin и т.д.) значения представляют собой не вещественные числа,  а

        числа типа Extended (с повышенной точностью).

 

               {$N+}

 

               begin

                 Writeln(Pi);                 { 3.14159265358979E+0000 }

               end.

 

               {$N-}

 

               begin

                 Writeln(Pi);                 { 3.1415926536E+00 }

               end.

 

             В оставшейся части данной главы обсуждаются специальные воп-

        росы,  касающиеся  использования  процессора  80x87  в программах

        Borland Pascal.

Типы данных процессора 80x87

             В дополнение к вещественному типу для программ, использующих

        средства процессора 80x87, предусматривается четыре новых вещест-

        венного типа:

 

             1.  Тип с одинарной точностью Single,  представляющий  собой

                 наименьший  формат,  который  вы можете использовать для

                 чисел с плавающей точкой.  Он занимает  4  байта  памяти

                 обеспечивает  диапазон представления чисел от 1.5х10^-45

                 до 3.4х10^48 с 7-8 значащими цифрами.

 

             2.  Тип с двойной точностью Double, занимающий 8 байт памяти

                 и  обеспечивающий  представление  чисел  в  диапазоне от

                 5.0х10^-334 до 1.7х10^308 с 15-16 значащими цифрами.

 

             3.  Тип с повышенной точностью Extended  представляет  собой

                 наибольший  формат представления чисел с плавающей запя-

                 той,  обеспечиваемый процессором 8087.  Он  занимает  10

                 байт памяти и обеспечивает диапазон  представления чисел

                 от  1.9х10^-4952 до 1.1х10^4932 с 19-20 значащими цифра-

                 ми.  Любые арифметические операции,  в которых участвуют

                 числа вещественного типа, выполняются с точностью и диа-

                 пазоном представления,  соответствующими типу с повышен-

                 ной точностью.

 

             4.  Числа сложного типа Comp используются для предварительно

                 объединенных значений в 8 байтах памяти, обеспечивая при

                 этом диапазон представления от -2^63+1  до  2^63-1,  что

                 составляет  приблизительно  от  -9.2х10^18 до 9.2х10^18.

                 Сложный тип можно сравнить с длинным целым типом  (двой-

                 ная точность),  но он считается вещественным типом, пос-

                 кольку при операциях с числами этого  типа  используется

                 сопроцессор 8087. Сложный тип хорошо подходит для предс-

                 тавления значений денежных единиц,  представляющих собой

                 сотни  и тысячи,  которые используются в прикладных ком-

                 мерческих программах.

 

             Независимо от того,  используете вы  сопроцессор  80x87  или

        нет,  6-битовый вещественный тип является допустимым. Таким обра-

        зом, при переходе к использованию сопроцессора 80 x87 вам не пот-

        ребуется изменять исходный текст программы, и вы можете использо-

        вать файлы данных,  созданные  программами,  которые  работают  с

        программно обеспечиваемыми операциями с плавающей точкой.

 

             Отметим, однако, что аппаратные вычисления с переменными ве-

        щественного типа выполняются несколько медленнее,  чем с перемен-

        ными  другого типа.  Это связано с тем,  что сопроцессор 80x87 не

        может непосредственно обрабатывать  вещественный  формат.  Вместо

        этого,  перед выполнением операций, для преобразования веществен-

        ных значений в числа с повышенной точностью требуются обращения к

        библиотечным  программам.  Если  вы заинтересованы в максимальной

        скорости выполнения и не собираетесь использовать свою  программу

        на  системах без сопроцессора 80x87,  то возможно вы захотите ис-

        пользовать вещественный тип с одинарной  точностью,  вещественный

        тип с двойной точностью,  вещественный тип с повышенной точностью

        и сложный типы явным образом.

Арифметические операции с повышенной точностью

             При использовании  сопроцессора  80x87 тип с повышенной точ-

        ностью Extended является основой всех операций с  плавающей  точ-

        кой.  В Турбо Паскале тип с повышенной точностью используется для

        представления всех нецелых числовых констант,  а также при вычис-

        лении всех выражений нецелого типа.  Например, в следующих опера-

        циях присваивания все правые части выражений  будут  вычисляться,

        как выражения с повышенной точностью, а затем их тип будет преоб-

        разован к типу соответствующей левой части:

 

              {$N+}

              var

                X, AA, B, C : real;

              begin

                X := (B + Sqrt(B*B - A*C))/A;

              end;

 

             Borland Pascal выполняет вычисления с точностью и диапазоном

        представления чисел,  соответствующими  типу  с  повышенной  точ-

        ностью,  без  дополнительных усилий программиста.  Дополнительная

        точность приводит к меньшим ошибкам округления,  а дополнительный

        диапазон означает,  что ситуации переполнения и потери значимости

        будут встречаться в программах реже.

 

             Вы можете обойтись и без дополнительных автоматических  воз-

        можностей вычислений с повышенной точностью Borland Pascal.  Нап-

        ример,  описать переменные,  использующиеся для промежуточных вы-

        числений, как переменные с повышенной точностью. В следующем при-

        мере вычисляется сумма произведений:

 

             var

               Sm : single;

               X,Y array[1..100] of single;

               I : integer;

               T : extended;            { для промежуточных результатов }

             begin

               T := 0.0;

               for I := 1 to 100 do T := T + X[I] * Y[I]

               Sum := T;

             end;

 

             Если бы переменная T была описана,  как переменная с одинар-

        ной точностью,  то при каждом цикле операции присваивания для пе-

        ременной  T были бы выполнены с ошибкой округления и ограничения-

        ми, соответствующими одинарной точности. Но, поскольку переменная

        T  является переменной с повышенной точностью,  то все ошибки ок-

        ругления (кроме операции, при которой значение переменной T прис-

        ваивается переменной Suм) имеют ограничения,  соответствующие по-

        вышенной точности.  Меньшие ошибки округления означают более точ-

        ный результат.

 

             Для значений  формальных  параметров и результата функции вы

        также можете задать повышенную точность. Это поможет избежать не-

        нужных преобразований типов чисел,  приводящих к потере точности.

        Например:

 

             function Area(Radius: extended): extended;

             begin

               Area := Pi * Radius * Radius;

             end;

Сравнение вещественных чисел

             Поскольку значения вещественного типа являются приблизитель-

        ными,  результат сравнения значений различного вещественного типа

        не  всегда можно предсказать.  Например,  если Х - переменная ве-

        щественного типа с одинарной точностью,  а Y - переменная вещест-

        венного типа с двойной точностью,  то результатом выполнения сле-

        дующих операторов будет значение False:

 

             X := 1/3;

             Y := 1/3;

             Writeln(X = Y);

 

             Причина этого состоит в том,  что Х имеет точность только до

        7-8 цифр, а Y - точность до 15-16 цифр, и когда оба значения пре-

        образуются к типу с повышенной точностью,  то  после  первых  7-8

        цифр остальные цифры будут различаться.  Аналогично,  результатом

        выполнения операторов:

 

             X := 1/3;

             Writeln(X = 1/3);

 

        будет значение False,  результат 1/3 в операторе Writeln вычисля-

        ется с точностью до 20 значащих цифр.

Стек вычислений сопроцессора 80x87

             У сопроцессора 80x87 имеется внутренний стек вычислений, ко-

        торый  может быть глубиной до восьми уровней.  Доступ к значению,

        находящемуся в стеке сопроцессора  80x87  осуществляется  намного

        быстрее, чем доступ к переменной в памяти, поэтому для достижения

        максимально возможной производительности в Borland  Pascal  внут-

        ренний  стек сопроцессора 80x87 используется для хранения времен-

        ных результатов и для передачи параметров процедурам и функциям.

             Теоретически, слишком сложные выражения  вещественного  типа

        могут вызвать переполнение стека сопроцессора 80x87. Однако этого

        не может случиться, поскольку для этого потребовалось бы, чтобы в

        выражении получалось более восьми промежуточных результатов.

 

             Более весомая опасность таится во вложенных вызовах функций.

        Если такие конструкции составлены некорректно, то они, вполне ве-

        роятно, могут привести к переполнению стека сопроцессора 80x87.

 

             Рассмотрим, следующую функцию,  в которой с помощью рекурсии

        вычисляются числа Фибоначчи:

 

             function Fib(N: integer): extended;

             begin

               if N = 0 then

                  Fib := 0.0

               else

                 if N = 1 then

                    Fib := 1.0

                 else

               Fib := Fib(N-1) + Fib(N-2);

             end;

 

             Обращение к  данной версии процедуры Fib приведет к перепол-

        нению стека сопроцессора 80x87, так как значений N больше, чем 8.

        Причина  заключается  в том,  что последний оператор присваивания

        требует временного сохранения результата выполнения процедуры Fib

        (N-1)  в  стеке сопроцессора 80x87.  Каждое рекурсивное обращение

        выделяется одна ячейка стека и на  девятом  обращении  произойдет

        переполнение стека. Корректной конструкцией в этом случае будет:

 

             function Fib(N : integer) : extended;

             var

               F1,F2 : Extended;

             begin

               if N = 0 then

                      Fib := 0.0

                  else

                    if N = 1

                       then Fib := 1.0

                    else

                      begin

                         F1 := Fib(N-1); F2 := Fib(N-2);

                         Fib := F1 + F2;

                      end;

             end;

 

             Временные результаты  теперь  сохраняются в переменных,  для

        которых отводится стек процессора 8086. (Стек процессора 8086 ко-

        нечно  тоже  может  переполниться,  но это обычно требует гораздо

        большего числа рекурсивных вызовов).

Запись вещественных чисел при использовании сопроцессора 80x87

             Если была указана директива {$N+},  то стандартные процедуры

        Write и Writeln, чтобы обеспечить представление в расширенном ди-

        апазоне,  выводят в строке с десятичными числами с плавающей точ-

        кой четыре цифры для показателя степени вместо двух.  Аналогично,

        стандартная процедура Str при выборе формата с  плавающей  точкой

        возвращает  значение  показателя  степени,  состоящее  из четырех

        цифр.

Модули, в которых используется сопроцессор 80x87

             Модули, в которых используется сопроцессор 80x87,  могут вы-

        зываться другими модулями или программами только  в  том  случае,

        если  эти  модули  или программы были скомпилированы с директивой

        {$N+}.  То, что модуль использует сопроцессор 80x87, определяется

        наличием в нем инструкций сопроцессора 80x87,  а не директивой $N

        во время компиляции.  Это позволяет компилятору быть более "снис-

        ходительным",  когда  вы  случайно компилируете модуль (в котором

        используется сопроцессор 80x87), не указав директиву {$N+}.

 

             Когда вы выполняете компиляцию в режиме кода 80х87 (директи-

        ва  {$N+}),  то  возвращаемые подпрограммами модуля Systем (Sqrt,

        Рi,  Sin и т.д.) значения представляют собой не вещественные чис-

        ла, а числа типа Extended (с повышенной точностью).

Распознавание сопроцессора 80х87 в программах DOS

             Исполняющая библиотека Borland  Pascal,  встроенная  в  вашу

        программу  (скомпилированную  с директивой {$N+}) включает в себя

        код инициализации,  который автоматически  распознает  наличие  в

        системе микросхемы сопроцессора 8087.  Если сопроцессор 8087 име-

        ется, то программа будет его автоматически использовать. В случае

        же его отсутствия программа будет использовать эмулирующую библи-

        отеку исполняющей системы.  Если программа компилировалась с  ди-

        рективой {$E-} и по время начала ее работы сопроцессор не обнару-

        живается,  то программа завершает  работу  с  сообщением  Numeric

        coprocessor  required  ("Требуется сопроцессор арифметических вы-

        числений").

 

             Есть несколько случаев,  когда вы возможно захотите изменить

        такую принятую  по  умолчанию  логику автоматического обнаружения

        сопроцессора. Например, в вашей системе может присутствовать соп-

        роцессор 8087, но вы захотите проверить, как будет работать прог-

        рамма,  предназначенная для функционирования на системах без соп-

        роцессора.   Или  же  потребуется  запустить  вашу  программу  на

        системе, совместимой с компьютером РС, но на этой системе при ра-

        боте  алгоритма автообнаружения будет выводиться некорректная ин-

        формация (например, будет сообщаться о наличие сопроцессора, ког-

        да на самом деле его нет, или наоборот).

 

             В Borland  Pascal  предусмотрена возможность отмены принятой

        по умолчанию логики автоматического  распознавания.  Эта  возмож-

        ность задается переменной операционной среды 87.

 

             Вы можете  установить переменную операционной среды 87 в от-

        вет на подсказку DOS с помощью команды SET,  например,  следующим

        образом:

 

             SET 87=Y

        или

             SET 87=N

 

             Установка для  переменной  операционной  среды 87 значения N

        (Нет) указывает коду инициализации, что вы не хотите использовать

        сопроцессор 8087, хотя он может и присутствовать в системе. И на-

        оборот: установка для переменной 87 значения Y (Да) означает, что

        сопроцессор имеется,  и вы хотите,  чтобы ваша программа его  ис-

        пользовала.  Однако  при этом нужно помнить о том,  что установка

        для переменной 87 значения Y при отсутствии в системе сопроцессо-

        ра 8087 приведет к тому, что ваша программа аварийно завершит ра-

        боту или "зависнет".

 

             Если переменная операционной среды 87 определена, а вы хоти-

        те,  чтобы она стала неопределенной,  то можно ввести в ответ  на

        подсказку DOS:

 

             SET 87=

        и нажать клавишу Enter.

 

             Если в  операционной среде DOS присутствует запись 87=Y, или

        если код инициализации успешно распознает сопроцессор,  то  далее

        код  инициализации выполняет последующие проверки,  чтобы опреде-

        лить,  какой это сопроцессор (8087, 80287 или 80387). Это необхо-

        димо  для того,  чтобы Турбо Паскаль мог корректно работать с от-

        дельными несовместимостями,  которые имеются между сопроцессорами

        различных типов.

 

             Результат автоматического распознавания наличия сопроцессора

        и его модели сохраняется в переменной Test8087 (которая  описыва-

        ется в модуле System). Для нее определены следующие значения:

 

               ЪДДДДДДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї

               і  Значение    і   Определение                  і

               ГДДДДДДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ

               і       0      і   сопроцессор не обнаружен     і

               і       1      і   обнаружен сопроцессор 8087   і

               і       2      і   обнаружен сопроцессор 80287  і

               і       3      і   обнаружен сопроцессор 80387  і

               АДДДДДДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ

 

             Чтобы определить характеристики системы, на которой работает

        ваша программа,  вы можете в программе проверить содержимое пере-

        менной Test8087.  В частности, эту переменную можно проанализиро-

        вать для того,  чтобы определить, эмулируются инструкции работы с

        плавающей точкой, или они действительно выполняются.

Распознавание сопроцессора 80x87 в программе Windows

             Операционная среда Windows и библиотека эмуляции WIN87EM.DLL

        автоматически  распознает  наличие  в  системе платы сопроцессора

        80x87. Если сопроцессор 80x87 имеется, то программа будет его ав-

        томатически  использовать.  В  случае же его отсутствия программа

        будет использовать эмуляцию с помощью WIn87EM.DLL.  Чтобы опреде-

        лить наличие в системе сопроцессора 80х87, вы можете использовать

        функцию GetWinFlags (которая определена в модуле WinProcs) и  би-

        товую маску wf_80x87 (определенную в модуле WinTypes). Например:

 

             if GetWinFlags and wf_80x87 <> 0 then

                 Writeln('80x87 присутствует') else

                 Writeln('80x87 отсутствует');

Использование эмуляции сопроцессора 80x87 на языке ассемблера

             Когда компоновка объектных файлов выполняется  с  директивой

        {$L имя_файла}, необходимо обеспечить, чтобы эти файлы компилиро-

        вать с разрешением эмуляции сопроцессора 80x87. Например, если вы

        используете инструкции сопроцессора 80x87 во  внешних  процедурах

        на языке ассемблера,  необходимо убедиться, что при ассемблирова-

        нии файлов .ASM в файлы .OBJ эмуляция разрешена. В противном слу-

        чае инструкции сопроцессора 80x87 не могут эмулироваться на маши-

        нах без сопроцессора 80x87.  Для разрешения эмуляции  используйте

        параметр командной строки Турбо Ассемблера /E.