ГЛАВА 14. ИСПОЛЬЗОВАНИЕ 8087. Существует два вида чисел, с которыми вы можете работать в Turbo Pascal: целые (Shortint, Integer, LongInt, Byte, Word) и вещественные (Real, Single, Double, Extended, Comp). Вещественные также известны, как числа с плавающей точкой. Процессор 8086 спроектирован так, что легко обрабатывает целые числа, но тратит значительно больше времени и усилий на обработку вещественных. Чтобы улучшить производительность обработки вещественных чисел, существует математический сопроцессор 8087. Сопроцессор 8087 - это специальный аппаратный числовой процессор, который может быть установлен в Вашу машину. Он выполняет операции с плавающей точкой очень быстро, если вы используете много таких вычислений, вам, вероятно, необходим сопроцессор. Turbo Pascal обеспечивает оптимальную производительность с плавающей точкой в зависимости от того, есть у вас 8087 или нет. - Для программ, работающих на любых ЭВМ (с 8087 или без), Turbo Pascal обеспечивает тип real и библиотеку программ, которая обрабатывает вещественные операции. Тип real занимает 6 байт памяти и обеспечивает диапазон от 2.9x(10** -39) до 1.7x(10**38) с 11-ю - 12-ю значащими цифрами. Программная библиотека с плавающей точкой оптимизирована по скорости и размеру, приближаясь в некоторых применениях r возможностям, обеспечиваемым 8087. - Если вам требуется дополнительная точность и гибкость 8087, вы можете инструктировать Turbo Pascal, генерировать код, который использует 8087. Это дает вам доступ к четырем дополнительным вещественным типам (Single, Double, Extended и Comp) и расширяет диапазон от 3.4x(10**-4951) до 1.1x(10**4932) с 19-ю - 20-ю значащими цифрами. Вы переключаете между двумя различными моделями вещественных чисел, используя директиву компилятора $N или кнопку 8087/80287 в диалоговом окне Options/Compiler. Значения по умолчанию - {$N-}, и в этом состоянии компилятор использует 6-и байтовую вещественную библиотеку, позволяя вам работать только с переменными типа Real. В состоянии {$N+} компилятор генерирует код для 8087, обеспечивая Вам повышенную точность и доступ к четырем дополнительным вещественным типам. Примечание: Когда программа компилируется в режиме N+, значения, возвращаемые вещественными программами модуля System (Sqrt, Pi, Sin и т.д.) будет типа Extended вместо Real: {$N+} begin WriteLn(Pi); {3.14159265358979} end. {$N-} begin WriteLn(Pi); {3.1415926536} end. Даже если на Вашей ЭВМ нет 8087, вы можете указать Turbo Pascal подключить библиотеку, которая эмулирует сопроцессор. В этом случае, если 8087 существует - он используется. Если не существует, он эмулируется программной библиотекой, что значительно медленнее. Директива компилятора $E и кнопка Emulation в диалоговом окне Options/Compiler используется для разрешения или запрещения эмуляции 8087. Значение по умолчанию - $Е+. И в этом состоянии полный эмулятор 8087 автоматически подключается к программе, которая использует 8087. В состоянии $E- используется значительно меньшая библиотека и результирующий .EXE файл может работать только на ЭВМ с 8087. Примечание: Директива $E не имеет эффекта, если стоит в модуле, она применяется только при компиляции программ. Следовательно, если программа откомпилирована в состоянии {$N-} и все модули, используемые программой, также откомпилированы с {$N-}, то библиотека 8087 не подключается и директива $E игнорируется. Далее в этой главе обсуждаются специальные вопросы, касающиеся программ на Turbo Pascal, которые используют 8087. Типы данных 8087. Для программ, которые используют 8087, Turbo Pascal обеспечивает четыре вещественных типа в дополнение к типу Rreal. - Тип Single имеет наименьшую длину, которую можно использовать для вещественного числа. Он занимает 4 байта памяти, обеспечивая диапазон от 1.5x(10** -45) до 3.4x(10**38) с 7-ю - 8-ю значащими цифрами. - Тип Double занимает 8 байт памяти, обеспечивая диапазон от 5.0х(10** -324) до 1.7х(10** 308) с 15-ю - 16-ю значащими цифрами. - Тип Extended - наибольший вещественный тип, поддерживаемый 8087. Он занимает 10 байт памяти, обеспечивая диапазон от 3.4х(10 * * -4932) до 1.1х(10** 4932) с 19-ю - 20-ю значащими цифрами. Любая арифметическая операция с вещественным типом производится с диапазоном и точностью типа Extended. - Тип Comp сохраняет целые значения в 8 байтах памяти, обеспечивая диапазон от -2 ** 63+1 до 2 ** 63-1, что примерно равно от - 9.2х(10**18) до 9.2х(10**18). Comp можно сравнить с LongInt двойной точности, но рассматривается как вещественный тип из-за того, что все операции Comp используют 8087. Comp удобен для использования в экономических расчетах. В независимости от того, есть ли 8087 или нет, 6-ти байтовый тип Real доступен всегда, так что вам не потребуется модифицировать исходный код для использования 8087. Вы можете всегда читать файлы с данными, сгенерированными программой, в которой использовалась программная библиотека вещественных чисел. Заметим однако, что 8087 обрабатывает переменные типа Real значительно медленнее, чем другие типы. Это происходит из-за того, что 8087 не может прямо обрабатывать формат Real. Вместо этого вызывается программа, преобразующая значение типа Real в тип Extended до операции над ним. Если Bы хотите оптимизировать скорость выполнения и никогда не будете использовать ЭВМ без 8087, вам лучше использовать только типы Single, Double, Extended и Comp. Расширенная арифметика. Тип Extended является основой для всех вычислений с плавающей точкой для 8087. Turbo Pascal использует формат Extended для хранения всех вещественных констант и вычисляет все вещественные выражения, используя точность Extended. Так например, вся правая часть следующего оператора, будет вычисляться в Extended до преобразования в тип левой части: {$N+} var X, A, B, C: Real; begin X := (B + Sqrt(B * B - A * C)) / A; end; Без специальных усилий программиста Turbo Pascal обеспечивает вычисления, используя точность и диапазон типа Extended. Дополнительная точность означает меньшую ошибку округления, и дополнительный диапазон означает более редкие ошибки переполнения. Вы можете улучшить автоматическое использование Extended Turbo Pascal. Например, вы можете объявить переменные типа Extended, используемые для промежуточных результатов. Следующий пример вычисляет сумму: var Sum: 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; Если бы Т была объявлена как Single, то каждое присваивание T в цикле давало бы ошибку округления из-за ограниченной точности Single. Но так как Т - Extended, то все ошибки округления ограничены точностью Extended за исключением присваивания T в Sum. Меньшая ошибка округления означает более точный результат. Вы можете также объявить формальные параметры и результат функций типа Extended. Это предотвращает необходимость преобразования между числовыми форматами, что могло бы привести к потере точности. Например: function Area(Radius: Extended): Extended; begin Area := Pi * Radius * Radius; end; Сравнение вещественных типов. Поскольку вещественные типы являются аппроксимированными, результат сравнения двух различных вещественных типов не всегда очевиден. Например, если X - переменная типа Single, а Y переменная типа Double, то следующие операторы будут давать False : X := 1/3; Y := 1/3; Writeln( X = Y); Это происходит из-за того, что X имеет точность 7 - 8 цифр, а Y - 15 - 16 ; и когда обе преобразуются в Extended, они имеют отличие после 7 - 8 цифр. Аналогично, операторы X := 1/3; Writeln( X = 1/3); будут выдавать False поскольку результат 1/3 в WriteLn будет вычисляться с 20-ю значащими цифрами. Стек вычисления 8087. 8087 имеет внутренний стек глубиной 8 уровней. Доступ к значению в стеке 8087 много быстрее, чем доступ к переменной в памяти. Чтобы достичь наибольшей производительности, Turbo Pascal использует стек 8087 для хранения временных результатов. Теоретически, очень сложные вещественные выражения могут привести к переполнению стека. Однако, этого не происходит, поскольку это требует, чтобы выражение генерировало более 8 промежуточных результатов. Опасность заключается в вызове рекурсивных функций. Если такие конструкции написаны некорректно, они могут привести к переполнению стека 8087. Рассмотрим следующую процедуру, которая вычисляет числа Фибоначчи, используя рекурсию. 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 будет приводить к переполнению стека 8087 при N > 8. Это происходит из-за того, что вычисление последнего присваивания требует сохранить в стеке результат Fib(N-1). Каждый рекурсивный вызов будет сохранять значения в стеке, приводя к переполнению на 9-ом вызове. Корректная конструкция дожна быть 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, конечно тоже может быть переполнен, но это обычно требует значительно больше рекурсивных вызовов). Вывод вещественных чисел с 8087. В состоянии {$N+} стандартные процедуры Write и WriteLn выводят 4 цифры вместо 2 для экспоненты числа с плавающей точкой, чтобы обеспечить диапазон для типа Еxtended. Кроме того, стандартная процедура Str возвращает 4 цифры экспоненты, когда выбран вещественный формат. Модули, использующие 8087. Модули, которые используют 8087, могут быть использованы только теми модулями и программами, которые откомпилированы в состоянии {$N+}. Определение, будет ли модуль использовать 8087, зависит от того, содержит ли он инструкции 8087, а не от того, в каком состоянии находится директива $N во время его компиляции. Это делает компилятор более "забывчивым" в случае, когда вы случайно компилируете модуль (который не использует 8087) в {$N+}. Примечание: Когда Вы компилируете в состоянии числовой обработки, {$N+} возвращаемое значение программ с плавающей точкой в модуле System - Sqrt, Pi, Sin и т.д. будут типа Extended вместо Real. Обнаружение 8087. Библиотека 8087 Turbo Pascal, встроенная в Вашу программу (компилированную с {$N+}) включает код, который автоматически определяет наличие 8087. Если 8087 присутствует - программа будет его использовать. Если его нет, то программа будет использовать библиотеку эмуляции. Если программа была откомпилирована в состоянии {$E-} и 8087 не обнаружен, то программа выдает: "Требуется сопроцессор" ("Numeric coprocessor required") и завершается. Бывают случаи, когда Вам необходимо отменить это автообнаружение. Например, ваша ЭВМ может иметь 8087, но Вы хотите проверить, как будет работать Ваша программа на машине без 8087. Или Вашей программе нужно работать на PC - совместимых ЭВМ и какие-то из них возвращают некорректное значение при автообнаружении (скажем, что 8087 присутствует, когда его нет, или наоборот). Turbo Pascal обеспечивает возможность для отмены автообнаружения. Это делается с помощью переменной 87 среды DOS. Вы устанавливаете значение переменной 87 от подсказки DOS, используя команду SET: SET 87 = Y или SET 87 = N Устанавливая переменную среды 87 в N, Вы говорите Turbo Pascal, что не хотите использовать 8087, даже если он есть на ЭВМ. Соответственно, присваивание переменной 87 = Y, означает, что сопроцессор есть и Вы хотите использовать его. ВНИМАНИЕ! Если Вы установили 87 = Y , а 8087 нет на ЭВМ, то выполнение Вашей программы приведет к аварийному завершению. Если переменная 87 была определена (любое значение) и Вы хотите отменить его, введите SET 87 = и после этого нажмите Enter. Если установлено 87 = Y или если автообнаружение успешно нашло сопроцессор, то код автообнаружения определяет, какой сопроцессор используется (8087, 80287 или 80387). Это необходимо для того, чтобы Turbo Pascal мог корректно обрабатывать различия, которые имеют эти сопроцессоры. Результат автообнаружения записывается в переменную Test8087 (которая объявлена в модуле System). Определены следующие значения : ──────────────────────────────────── Значение Описание ──────────────────────────────────── 0 сопроцессора нет 1 обнаружен 8087 2 обнаружен 80287 3 обнаружен 80387 ──────────────────────────────────── Ваша программа может проверить переменную Test8087, чтобы определить конфигурацию ЭВМ. В частности, Test8087 может быть проверена для того, чтобы определить - вещественные операции эмулируются или выполняются напрямую. Эмуляция 8087 в Ассемблере. Когда подключается объектный файл (используя директиву {$L filename}), то необходимо, чтобы этот объектный файл был откомпилирован с эмуляцией 8087. Например, если Вы используете вещественные операции во внешней процедуре, написанной на Ассемблере, то необходимо, чтобы была включена эммуляция, когда Вы ассемблируете файл .ASM в файл .OBJ. В противном случае инструкции 8087 не смогут быть эмулированы на машине без 8087. Для включения эмуляции используйте опцию /Е Turbo Assembler.