46019 (665326), страница 30
Текст из файла (страница 30)
Однако, семейство процессоров iAPx86 имеет сопутствующееему семейство математических сопроцессоров, 8087, 80287 и
80387. Мы будем обозначать все семейство математических сопроцессоров 80x87 термином "сопроцессор".
В случае процессора80487вы имеете математический сопроцессор уже встроенным в основной.
80х87 представляет собой специальный аппаратно-реализованный числовой процессор, которыйможно установить на вашем PC. Он служит для выполненияс большой скоростью команд с плавающей точкой. При большомколичестве в вашей программе операций с плавающей точкой вам, безусловно,нуженсопроцессор. Блок центрального процессора в вашем компьютере осуществляет интерфейс с 80х87 по специальным шинам интерфейса.
Эмулирование платы 80х87
По умолчанию в Turbo C++ устанавливается опция генерации кода "эмуляция" (опция компилятора командной строки -f). Эта опция предназначена для программ, которые могут вообще не иметь операций с плавающей точкой, а также для программ, которые должны идти и на машинах, на которых сопроцессор 808 х87 не установлен.
В случае опции эмуляции компилятор генерирует код, как если бы сопроцессор имелся, но при компоновке подключает библиотеку эмуляции операций сплавающей точкой (EMU.LIB). При выполнении такой программы сопроцессор 80х87, если он установлен, будет использоваться; если же во время выполнения процессора не окажется, то программа будет использовать специальноепрограммное обеспечение, эмулирующее 80х87.
Получение кода только для машин с 80х87
Если вы планируете использовать вашу программу исключительно на машинах с установленным математическим сопроцессором 80х87, то можно сэкономить около 10К памяти программы, опустив из нее логику автоматического определения присутствия 80х87 и эмулятора. Для этого следует просто выбратьопцию генерации кодаопераций с плавающей точкой при наличии 80 х87 (или опциюкомпилятора командной строки -f87). Turbo C++ в этом случае скомпонует вашу программу с библиотекой FP87.LIB вместо EMU.LIB.
Получение кода без операций с плавающей точкой
При отсутствии в программе операций с плавающей точкой вы можете сэкономить немного времени компиляции, выбрав опцию генерации операций с плавающей точкой None ("отсутствуют") (или опцию компилятора командной строки -f-). Тогда Turbo C++ не будет выполнять компоновку ни с EMU.LIB, ни с FP87.LIB, ни с MATHx.LIB.
Опция быстрых вычислений с плавающей точкой
Turbo C++ имеет опцию быстрых вычислений с плавающей точкой (опция компилятора командной строки -ff). Выключить эту опцию можнопри помощи опции командной строки -ff-. Ее назначение состоит в выполнении некоторой оптимизации, противоречащей правильной семантике С. Например,
double x;
x = (float)(3.5*x);
Для вычисления по обычным правилам x умножается на 3.5, давая точность результата double,которая затем усекается до точности float, после чего x записывается какdouble. При использовании опции быстрых вычислений с плавающей точкой произведение типа long double преобразуетсянепосредственно в double. Поскольку лишь очень немногие программы чувствительны к потере точностипри преобразовании от более точного к менее точному типу с плавающей точкой, то данная опция является умолчанием.
Переменная операционной среды 87
При построении программы с эмуляцией 80x87, которая устанавливается по умолчанию, ваша программа станет автоматически проверять наличие 80х87 и использовать его, если он установлен в машине.
Существует ряд ситуаций, в которых вам может понадобиться отменить режим автоматического определения наличия сопроцессора по умолчанию. Например, вашасобственная исполняющая система можетиметь 80х87, но вам требуется проверить, будет ли программа работать так, как вы предполагали, в системебез соопроцессора. Либоваша программа предназначена дляработыв системе, совместимой с PC, но данная конкретная система возвращает логике автоматического определения наличия сопроцессора неверную информацию (либо при оссутствии 80х87 говорит, что он на месте, либо наоборот).
Turbo C++ имеет опцию для переопределения логики определения наличия сопроцессорапри загрузке программыж эта опция - соответствующая переменная операционной среды системы
87.
Переменная операционной среды 87 устанавливается по приглашению DOS при помощи команды SET:
C>SET 87=N
или
C>SET 87=X
Ни с какой стороны знака равенства не должно быть пробелов. Установка переменной операционной среды 87 в N (это значит "Нет") говорит загрузочному коду исполняющей системы о том,что вы не хотите использовать 80х87 даже в том случае, если он установлен в системе.
Установка переменной операционной среды в Y ("Да") означает, что сопроцессор на месте и вы желаете, чтобы программа его использовала.Программист должен знать следующее: !!! Если установить 87=Y, а физически 80х87 в системе не установлен, то система повиснет.
Если переменная операционной среды 86 была определена (в любоезначение), и вы желаете сделать ее неопределенной, введите на приглашение DOS:
C>SET=
Непосредственно после знака равенстванажмите Enter, и переменная 87 станет неопределенной.
Регистры и 80х87
Существует два момента, связанных с использованием регистров, которые вы должны учитывать при работе с плавающей точкой:
1. В режиме эмуляции 80Х87 циклический переход в регистрах, а также ряд других особенностей 80х87 не поддерживается.
2. Если вы смешиваете операции с плавающей точкой и встроенные коды на языке ассемблера, то при использовании регистров вы должны принимать некоторые меры предосторожности. Это связано с тем, что набор регистров 80х87 перед вызовом функции в Turbo C++ очищается. Вам может понадобиться извлечь из стека и сохранить регистры 80х87 до вызова функции, использующей сопроцессор, если вы не уверены, что свободных регистров достаточно.
Отмена обработки особых ситуаций
для операций с плавающей точкой -------------------------
По умолчанию программа на Turbo C++ в случае переполнения или деления наноль в операциях сплавающей точкой аварийно прерывается. Вы можете замаскировать эти особые ситуациидля операций с плавающей точкой, вызывая в main _control87 перед любой операцией с плавающей точкой. Например,
#include
main() (*
_control87(MCW_EM,MCW_EM);
...
*)
Вы можете определить особую ситуацию для операции с плавающей точкой, вызвав функции _status87 или _clear87. См. описания этих функций в Главе 1 Справочника по библиотеке.
Определенные математические ошибки могуттакже произойти в библиотечных функциях; например, при попытке извлечения квадратного корня из отрицательного числа. Поумолчанию в таких случаях выполняется вывод на экран сообщений об ошибке и возврат значения NAN (код IEEE "not-a-number -- "не-число"). Использование NAN скорее всего приведет далее к возникновению особой ситуации с плавающей точкой, которая в свою очередь вызовет , если она не замаскирована, аварийное прерывание программы. Если вы не желаете, чтобысообщение выводилось на экран, вставьте в программу соответствующую версию matherr.
#include
int cdecl matherr(struct exception *e)
(*
return 1; /* ошибка обработана */
*)
Любое другое использование matherr для внутренней обработки математических ошибок недопустимо,так как она считается устаревшей иможет не поддерживаться последующими версиями Turbo C++.
Математические операции с комплексными числами
Комплексными называются числа вида x +yi, где x и yэто действительные числа, а i это корень квадратный из -1. В Turbo C++ всегда существовал тип
struct complex
(*
double x, y;
*);
определенный в math.h. Этот тип удобен для представления комплексных чисел, поскольку их можно рассматривать в качестве пары действительных чисел. Однако, ограничения С делают арифметические операции с комплексными числами несколько громоздкими. В С++ операции с комплексными числами выполняются несколько проще.
Для скомплексными числами в С++ достаточно включить complex.h. В complex.h для обработкикомплексных числе перегружены:
- все обычные арифметические операции
- операции потоков >> и <<
- обычные арифметические функции, такие как sqrt и log
Дополнительную информацию см. в описании класса complex в Справочнике по библиотеке.
Библиотека complex активизируется только при наличии аргументов типа complex. Таким образом, для получении комплексного квадратного корня из -1 используйте
sqrt(complex(-1))
а не
sqrt(-1)
Примером вычислений с комплексными числами служит следующая функция, выполняющая комплексное преобразование Фурье:
#include
// вычисление дискретного преобразования Аурье для
// a[0],...,a[n-1]
void Fourier(int n, complex a[], complex b[])
(*
int j,k;
complex i(0,1); // корень квадратный из -1
for (j = 0; j < n; ++j)
(*
b[j] = 0;
for (k = 0; k < n; ++k)
b[j] += a[k] * exp(2*M_PI*j*k8i/n);
b[j] /= sqrt(n);
*)
*)
Использование двоично-десятичной (BCD) математики
Turbo C++, также как и большинство прочик компьютеров и компиляторов, выполняет математические вычисления с числами в двоичном представлении (то есть в системе счисления с основанием 2). Это иногда путает людей, привыкших исключительно к десятичной математике (в системе счисления с основанием
10). Многие числа с точным представлением в десятичной системе счисления, такиекак 0.01,в двоичной системе счисления могут иметь лишь приближенные представления.
Двоичныечисла предпочтительныв большинстве прикладных программ, однако внекоторых ситуациях ошибка округления в преобразованиях меджу системами счисления с основаниями 2 и 10 нежелательна. Наиболее характерным случаев здесь являются финансовые или учетные задачи, где предполагается сложение центов. Рассмотрим программу, складывающую до 100 центов и вычитающую доллар:
#include
int i;
float x =0.0;
for (i = 0; i < 100; ++i)
x += 0.01;
x -= 1.0;
print("100*.01 - 1 = %g\1",x);
Правильным ответом является 0.0, однако ответ, полученный данной программой, будет малой величиной, близкой к 0.0. При вычислении ошибка округления, возникающая во время преобразования 0.01 в двоичное число, накапливается. Изменение типа x на double или long double только уменьшаетошибкувычисления, но не устраняет ее вообще.
Для решения этой проблемы Turbo C++ предлягаетспецифичный для C++ типbcd (двоично-десятичный), объявленный в bcd.h. В случае двоично-десятичного представления число 0.01 будет иметь точное представление, а переменная x типа bcd даст точное исчисление центов.
#include
int i;
bcd x = 0.0;
for (i = 0; i < 100; ++i)
x += 0.01;
x -= 1.0;
cout << "100*0.1 - 1 = " << x << "\n";
При этом необходимо учитывать следующие особенности типа bcd:
- bcd не уничтожает ошибку округления вообще. Вычисление типа 1.0/3.0 все равно будет иметь ошибку округления.
- Обычные математические функции, такие как sqrt и log, для аргументов с типом bcd перегружаются.
- Числа типа bcd имеют точность представления около 17 разрядов и -125 125 диапазон принимаемых значений от 1x10 до 1x10.
Преобразования двоично-десятичных чисел
bcd - это определяемый тип, отличный отfloat,doubleили long double;десятичная арифметика выполняется только когда хотя бы один операнд имеет тип bcd.
Важное замечание !
Для преобразования двоично-десятичногочисла обратно, к обычной системе счисления с основанием 2 (тип float, double или long double), служит функция-компонент real класса bcd;это преобразование не выполняется автоматически. real выполняет все необходимые преобразования к long double, который может быть затем преобразован к другим типам при помощи обычных средств С. Например,
bcd a = 12.1;
можно вывести при помощи любой из приводимых ниже строк кода:
double x = a; printf("a = %g", x);
printf("a = %Lg", real(a));
printf("a = %g", (double)real(a));
cout << "a =" << a;
Отметим,что посколькуprintfне выполняет контроль типа аргументов, спецификатор формата должен иметь L, если передается значение real(a) типа long double.
Число десятичных знаков
Вы можете задать, сколькодесятичных знаков должно участвовать в преобразовании из двоичного типа в bcd. Это число является вторым, опциональным аргументом в конструкторе bcd. Например, для преобразования $1000.00/7в bcd-переменную, округленную до ближайшего цента, можно записать:
bcd a = bcd(1000.00/7, 2)
где 2 обозначает два разряда после десятичной точки. Таким образом,
1000.00/7= 142.85714 bcd(1000.00/7, 2)= 142.860
bcd(1000.00/7, 1)= 142.900 bcd(1000.00/7, 0)= 142.000 bcd(1000.00/7, -1)= 140.000
bcd(1000.00/7, -2)= 100.000
Округление происходит по банковским правилам, что означает оаругление до ближайшего целого числа, причем в случае одинакового "расстояния" до ближайшего целого в прямую и обратную сторону округление выполняется в сторону четного. Например,
bcd(12.335, 2)= 12.34
bcd(12.245, 2)= 12.34
bcd(12.355, 2)= 12.36
Использование оперативной памяти Turbo C++
При компиляции Turbo C++ не генерирует каких-либо промежуточных структур данных на диске (Turbo C++ пишет на диск только .OBJ-файлы); между проходами он помещает промежуточные структуры данных в оперативную память. Вследствие этого вы можете получить сообщение"Out of memory...", что означает, что компилятору не хватает памяти.
Решение данной проблемы состоит в том, чтобы сделать ваши функции меньше по размеру, либо разбить на несколько частей файл с большими функциями. Можно также удалить из памяти ранее размещенные там резидентные программы, чтобы освободить больше памяти для использования Turbo C++.
Оверлеи (VROOMM)
Оверлеями называются части кода программы, разделяющие общую область памяти. Одновременно в памяти находятся только те части программы, которые в текущий момент нужны данной функции.
Оверлеи могут существенно снизить требования к выделяемой программе во время выполнения памяти. При помощи оверлеев можно создавать программы,значительно превышающие по размеру общую доступную память системы, поскольку одновременно в памяти находится лишь часть данной программы.
Работа программ с оверлеями
Программа управления оверлеями (VROOMM, или Virtual Run -time Object-Oriented Memory Manager) очень сложна; она выполняет за вас большую часть работы по организации оверлеев. В обычных оверлейных системах модули группируются в базовый и набор оверлейных блоков. Подпрограммы в данном оверлейном блоке могут вызывать подпрограммы из этого жеблока и из базового блока, но не из других блоков. Оверлейные блоки перекрываются друг с другом; т.е. одновременно в памяти может находиться только один оверлейный блок, и все они при активизации занимаютодин и тот же участок физической памяти. Общий объем памяти, необходимой длязапуска данной программы, определяется размером базового, плюс максимального оверлейного блока.
Эта обычная схема не обеспечивает достаточной гибкости. Она требует полного учета всехвозможных обращений между модулями программы и соответственной, планируемойвами,группировки оверлеев. Если вы не можете разбить вашу программу в соответствии со взаимозависимостями обращений между ее модулями, то вы не сможете и разбить ее на оверлеи.
Схема VROOMM совершенно иная. Она обеспечивает динамический свопинг сегментов. Базовым блоком свопинга является сегмент. Сегмент может состоять из одного или нескольких модулей. И что еще более важно, любой сегмент может вызывать любой другой сегмент.
Вся память делится на базовую область и область свопинга. Как только встречается вызов функции, котораяне находится ни в базовой, ни в областисвопинга, сегмент, содержащий вызываемую функцию, помещается в обдасть свопинга, возможно, выгружая оттуда при этом другие сегменты. Это мощное средство - подобное виртуальной программной памяти. От вас больше не требуется разбивать кодна статические, отдельные оверлейные блоки. Вы просто запускаете программу!
Что происходит, когдавозникает необходимость поместить сегмент в область свопинга? Если эта область имеет достаточно свободного места, то данная задача выполняется просто. Если же нет, то из области свопинга должно быть выгружено один или более сегментов, чтобы искомая свободнаяобласть освободилась.Как выбрать сегменты для выгрузки? Действующий здесь алгоритм очень сложен. Упрощенная версия его такова: если в области свопинга имеется неактивный сегмент,то для выгрузки выбираетсяон. Неактивными считаются сегменты, в которых в текущий момент нет выполняемых функций.В противном случае берется активный сегмент. Удаление сегментов из памяти продолжаетсядо тех пор, пока в области свопинга не образуется достаточно свободнойпамятидля размещения там требуемогосегмента. Такой метод называется динамическим свопингом.
Чем больше памяти выделено для области свопинга, тем лучше работает программа. Область свопинга работает как кэшпамять: чем больше кэш, тем быстрее работает программа.Наилучшие значения размера области свопинга определяются размерами рабочего множества данной программы.