10 Арифметический сопроцессорЕще одно устройство, которое мы опишем в этом томе - арифметический сопроцессор фирмы Intel. В старых моделях компьютеров сопроцессор устанавливался на системной плате в отдельной панельке и подключался непосредственно к центральному процессору. Современные процессоры Pentium содержат встроенный арифметический сопроцессор. Арифметический сопроцессор предназначен для выполнения операций над числами в формате с плавающей точкой (вещественные числа) и длинными целыми числами. Он значительно (в десятки раз) ускоряет вычисления, связанные с вещественными числами. Сопроцессор может вычислять такие функции, как синус, косинус, тангенс, логарифмы и так далее. Разумеется, что с помощью сопроцессора можно выполнять и простейшие арифметические операции сложения, вычитания, умножения и деления. Основная область применения арифметического сопроцессора - научные расчеты и машинная графика. Некоторые пакеты САПР, например, Autocad, отказываются работать, если в машине отсутствует сопроцессор. Сопроцессор запускается центральным процессором. После запуска он выполняет все вычисления самостоятельно и параллельно с работой центрального процессора. Если центральный процессор выдает очередную команду сопроцессору в момент времени, когда тот еще не закончил выполнение предыдущей команды, центральный процессор переводится в состояние ожидания. Если же сопроцессор ничем не занят, центральный процессор, выдав команду сопроцессору, продолжает свою работу, не дожидаясь завершения вычисления. Впрочем, есть специальные средства синхронизации (команда FWAIT). Как программировать сопроцессор? Команды, предназначенные для выполнения сопроцессором, записываются в программе как обычные машинные команды центрального процессора. Но все эти команды начинаются с байта, соответствующего команде центрального процессора ESC. Встретив такую команду, процессор передает ее сопроцессору, а сам продолжает выполнение программы со следующей команды. Ассемблерные мнемоники всех команд сопроцессора начинаются с буквы F, например: FADD, FDIV, FSUB и так далее. Команды сопроцессора могут адресоваться к операндам, аналогично обычным командам центрального процессора. Операндами могут быть либо данные, расположенные в основной памяти компьютера, либо внутренние регистры сопроцессора. Для команд арифметического сопроцессора возможны все виды адресации данных, используемые центральным процессором. Прежде чем начать обсуждение команд, выполняемых сопроцессором, приведем форматы данных. Как мы уже говорили, сопроцессор может работать либо с данными в формате с плавающей точкой, либо с целыми числами. В следующем разделе мы рассмотрим форматы чисел с плавающей точкой или форматы вещественных чисел. Вещественные числаПеред тем как приступить к изучению форматов вещественных чисел, используемых сопроцессором, вспомним о числах с плавающей точкой, встречающихся в научных расчетах. В общем виде эти числа можно записать следующим образом: (знак)(мантисса)*10(знак)(порядок) Например: -1.35*105. Здесь знак - это минус, мантисса - 1.35, порядок - 5. Порядок тоже может иметь знак. В этом представлении чисел для вас вряд ли есть что либо новое. Вспомним также такое понятие, как норамализованное представление чисел: · если целая часть мантиссы числа состоит из одной цифры, не равной нулю, то число с плавающей точкой называется нормализованным В чем преимущества использования нормализованных чисел? В том, что для фиксированной разрядной сетки числа (то есть для фиксированного количества цифр в числе) нормализованные числа имеют наибольшую точность. Кроме того, нормализованное представление исключает неоднозначность - каждое число с плавающей точкой может быть представлено различными (ненормализованными) способами: 123.5678*105 = 12.35678*106 = 1.235678*107 = 0.1235678*108 Для тех, кто программировал на языках высокого уровня, знакомо следующее представление чисел с плавающей точкой: (знак)(мантисса)E(знак)(порядок) Например, -5.35E-2 означает число -5.35*10-2. Такое представление называется научной нотацией. Арифметический сопроцессор может работать с вещественными числами в трех форматах: · одинарной точности; · двойной точности; · расширенной точности Эти числа занимают в памяти, соответственно, 4, 8 или 10 байт (рис. 10.1). Рис. 10.1. Различные представления вещественных чисел В любом представлении старший бит определяет знак вещественного числа: · 0 - положительное число; · 1 - отрицательное число Все равные по абсолютному значению положительные и отрицательные числа отличаются только этим битом. В остальном числа с разным знаком полностью симметричны. Для представления отрицательных чисел здесь не используется дополнительный код, как это сделано в центральном процессоре. Арифметический сопроцессор работает с нормализованными числами, поэтому поле мантиссы содержит мантиссу нормализованного числа. Так как здесь используется двоичное представление чисел, сформулируем определение нормализованного числа для двоичного представления: · если целая часть мантисса числа в двоичном представлении равна 1, то число с плавающей точкой называется нормализованным Так как для нормализованного двоичного числа целая часть всегда равна единице, то эту единицу можно не хранить. Именно так и поступили разработчики арифметического сопроцессора - в форматах одинарной и двойной точности целая часть мантиссы не хранится. Таким образом экономится один бит памяти. Для наглядности представим мантиссу числа в следующей форме: n.nnnnnnnnnn...n Здесь символом n обозначается либо 0, либо 1. Нормализованные числа в самой левой позиции содержат 1, поэтому их можно изобразить еще и в таком виде: 1.nnnnnnnnnn...n Представление с расширенной точностью используется сопроцессором для выполнения всех операций. И даже более - все операции с числами сопроцессор выполняет над числами только в формате с расширенной точностью. В этом формате хранится и "лишний" бит целой части нормализованного числа. Основная причина использования для вычислений расширенной точности - предохранение программы от возможной потери точности вычислений, связанной с большими различиями в порядках чисел, участвующих в арифметических операциях. Поле порядка - это степень числа 2, на которую умножается мантисса, плюс смещение, равное 127 для одинарной точности, 1023 - для двойной точности и 16383 - для расширенной точности. Для того, чтобы определить абсолютное значение числа с плавающей точкой, можно воспользоваться следующими формулами: Одинарная точность: 1.(цифры мантиссы)*2(P-127) Двойная точность: 1.(цифры мантиссы)*2(P-1023) Расширенная точность: 1.(цифры мантиссы)*2(P-16383) Знак числа, как мы уже говорили, определяется старшим битом. Приведем конкретный пример. Пусть мы имеем число с одинарной точностью, которое в двоичном виде выглядит следующим образом: 1 01111110 11000000000000000000000 Для этого числа знаковый бит равен 1 (отрицательное число), порядок равен 126, мантисса - 11 (в двоичной системе счисления). Значение этого числа равно: 1.11 * 2(126-127) = -1.75 * 2-1 = -0,875 Рассмотрим теперь особые случаи представления вещественных чисел. · нуль - это такое число, у которого порядок и мантисса равны нулю. Нуль может иметь положительный или отрицательный знаки, которые игнорируются в операциях сравнения. Таким образом, имеется два нуля - положительный и отрицательный; · наименьшее положительное число - это число, которое имеет нулевой знаковый бит, значение порядка, равное 1, и значение мантиссы, равное нулю. В зависимости от представления наименьшее положительное число имеет следующие значения: 1,17*10-38 (одинарная точность), 2.23*10-308 (двойная точность), 3.37*10-4932 (расширенная точность); · наибольшее отрицательное число - полностью совпадает с наименьшим положительным числом, но имеет бит знака, установленный в 1; · наибольшее положительное число - это число, которое имеет нулевой знаковый бит, поле порядка, в котором все биты кроме самого младшего, равны 1, и содержит единицы во всех разрядах мантиссы. В зависимости от представления наибольшее положительное число имеет следующие значения: 3.37*1038 (одинарная точность), 1.67*10308 (двойная точность), 1.2*104932 (расширенная точность); · наименьшее отрицательное число - полностью совпадает с наибольшим положительным числом, но имеет бит знака, установленный в 1; · положительная и отрицательная бесконечность - это число содержит все единицы в поле порядка и все нули в поле мантиссы. В зависимости от состояния знакового бита может быть положительная и отрицательная бесконечности. Бесконечность может получиться, например, как результат деления конечного числа на нуль; · нечисло - содержит все единицы в поле порядка и любое значение в поле мантиссы. Нечисло может возникнуть в результате выполнения неправильной операции при замаскированных особых случаях (ошибкам при работе с сопроцессоре будет посвящен отдельный раздел этой главы); · неопределенность - содержит в поле порядка все единицы, а в поле мантиссы - число 1000..0 (для одинарной и двойной точности) или 11000..0 (для расширенной точности, так как в этом формате хранится старший бит мантиссы). Для большей наглядности сведем все возможные представления вещественных чисел вместе на рис. 10.2. Рис. 10.2. Возможные предстваления вещественных чисел Целые числаАрифметический сопроцессор наряду с вещественными числами способен обрабатывать и целые числа. Он имеет команды, выполняющие преобразования целых чисел в вещественные и обратно. Возможно четыре формата целых чисел: · целое число; · короткое целое число; · длинное целое число; · упакованное десятичное число Целое число занимает два байта. Его формат полностью соответствует используемому центральным процессором. Для представления отрицательных чисел используется дополнительный код. Короткое целое и длинное целое имеют аналогичные форматы, но занимают, соответственно, 4 и 8 байт. Упакованное десятичное число занимает 10 байт. Это число содержит 18 десятичных цифр, расположенных по две в каждом байте. Знак упакованного десятичного числа находится в старшем бите самого левого байта. Остальные биты старшего байта должны быть равны 0. Существуют команды сопроцессора, которые преобразуют числа в формат упакованных десятичных чисел из внутреннего представления в расширенном вещественном формате. Если программа делает попытку преобразования в упакованный формат денормализованных чисел, нечисел, бесконечности и тому подобных, в результате получается неопределенность. Неопределенность в упакованном формате представляет из себя число, в котором два старших байта содержат единицы во всех разрядах. Содержимое остальных восьми байтов произвольно. При попытке использовать такое упакованное число в операциях фиксируется ошибка. Мы подробно рассмотрели формат представления вещественных чисел и отметили, что в этом формате для представления отрицательных чисел используется специальный знаковый бит. Для целых чисел используется дополнительный код. В дополнительном коде положительные числа содержат нуль в самом старшем бите числа: 0XXX XXXX XXXX XXXX Для получения отрицательного числа в дополнительном коде из положительного надо инвертировать каждый бит числа и затем прибавить к числу единицу. Например, число +5 в дополнительном коде выглядит следующим образом: 0000 0000 0000 0101 = +5 Для получения числа -5 вначале инвертируем значение каждого бита: 1111 1111 1111 1010 Теперь прибавим к полученному числу +1: 1111 1111 1111 1011 = -5 На рис. 10.3 мы привели все возможные варианты представления целых чисел. Рис. 10.3. Возможные представления целых чисел Формат упакованного десятичного числа показан на рис. 10.4. Рис. 10.4. Формат упакованного десятичного числа На этом рисунке n0...n17 означают разряды десятичного числа. Они могут изменяться в пределах от 0000 до 1001, то есть от 0 до 9 в десятичной системе счисления. Теперь, после того как мы рассмотрели форматы данных, с которыми может работать арифметический сопроцессор, можно перейти к изучению внутренних регистров сопроцессора. Регистры сопроцессораАрифметический сопроцессор содержит восемь численных 80-битовых регистров, предназначенных для хранения промежуточных результатов вычислений, регистра управления, регистра состояния, регистра тегов, регистра указателя команды и регистра указателя операнда. Численные регистрыМы будем обозначать численные регистры как ST0 - ST7. Они приведены на рисунке 10.5. Рис. 10.5. Численные регистры арифметического сопроцессора Численные регистры используются как стек. Регистр состояния в поле ST содержит номер численного регистра, являющего вершиной стека. При выполнении команд в качестве операнда могут выступать численные регистры. В этом случае номер указанного в команде регистра прибавляется к содержимому поля ST регистра состояния и таким образом определяется используемый регистр. Большинство команд после выполнения увеличивают поле ST регистра состояния, как бы записывая результаты своей работы в стек численных регистров. Вы можете использовать регистры как массив, но в этом случае необходимо заботится о постоянстве поля ST регистра состояния, так как в противном случае номера численных регистров будут изменяться. Регистр теговЭтот регистр разделен на восемь двухбитовых полей, которые мы обозначим как TAG0...TAG7. Каждое поле относится к своему численному регистру (рис. 10.6). Рис. 10.6. Формат регистра тегов Поля регистра тегов классифицируют содержимое "своего" численного регистра:
Например, если все регистры сопроцессора были пустые, а затем в стек численных регистров было занесено одно действительное ненулевое значение, содержимое регистра тегов будет 3FFFh. Регистр управленияРегистр управления для сопроцессора 8087 показан на рисунке 10.7. Рис. 10.7. Формат регистра управления для сопроцессора 8087 Регистр управления сопроцессоров 80287/80387 и сопроцессора, входящего в состав более современных процессоров, имеет аналогичный формат, за исключением того, что бит 7 в нем не используется (рис. 10.8). Рис. 10.8. Формат регистра управления для современных сопроцессоов Биты 0...5 - маски особых случаев. Особые случаи иногда возникают при выполнении команд сопроцессора, например, при делении на нуль, переполнении и так далее. Если все биты масок особых случаев равны нулю, особый случай вызывает прерывание центрального процессора INT 10h (обратите внимание, что это прерывание используется BIOS для работы с дисплейным адаптером). Если же особые случаи замаскированы установкой соответствующих бит в единичное состояние, прерывание не вырабатывается, а в качестве результата возвращается особое значение - бесконечность, нечисло и так далее. Приведем таблицу масок особых случаев:
Подробнее особые случаи и условия их возникновения будут описаны позже, когда мы займемся ошибками при выполнении команд в сопроцессоре. Поле PC управляет точностью вычислений в сопроцессоре:
Искусственное ухудшение точности вычислений не приводит к ускорению работы программы. Режимы с пониженной точностью предназначены для эмуляции процессоров, использующих двойную и одинарную точность, соответственно. Двух битовое поле RC задает режим округления при выполнении операций с вещественными числами:
Ниже демонстрируются перечисленные выше режимы округления. Символами "o" обозначены точные значения вещественных чисел, символами "x" приближенные значения. Стрелки "<<" и ">>" указывают направление округления. В центре линии расположен нуль числовой оси, на ее левом и правом конце - отрицательная и положительная бесконечности. · Округление в направлении к ближайшему числу: -беск.<-o-<<-x-------o---- 0 -----o-----x->>--o---->+беск. · Округление в направлении к отрицательной бесконечности: -беск.<-o-<<-x-------o---- 0 -----o---<<----x-o---->+беск. · Округление в направлении к положительной бесконечности: -беск.<-o-x-->>------o---- 0 -----o------x->>-o---->+беск. · Округление в направлении к нулю: -беск.<-o-x-->>------o---- 0 -----o---<<----x-o---->+беск. Для наибольшего уменьшения ошибок вычислений целесообразно использовать режим округления в направлении к ближайшему числу. Режим округления в направлении к нулю используется при моделировании целочисленной арифметики. Остальные два режима округления используют в интервальной арифметике. Для получения наиболее точного результата каждая команда (операция) выполняется два раза - первый раз с округлением в направлении к отрицательной бесконечности, второй раз - в направлении к положительной бесконечности. Точный результат лежит между полученными значениями. Заметьте, что здесь речь идет только об отелных операциях, но не о том, чтобы выполнить всю программу вычислений вначале с одним режимом округления, а затем с другим. Поле IC регистра управления предназначен для управления бесконечностью и может иметь два значения: · 0 - проективный режим; · 1 - афинный режим В проективном режиме существует только одна бесконечность, она не имеет знака. В афинном режиме имеется две бесконечности - положительная и отрицательная. Афинный режим допускает выполнение многих операций с бесконечностями - сложение, умножение и так далее. Регистр состоянияПоля регистра состояния сопроцессора 8087 показаны на рисунке 10.9. Рис. 10.9. Формат регистра состояния сопроцессора 8087 Регистр состояния сопроцессоров 80287/80387 и сопроцессора, входящего в состав современных процессоров, имеет немного другой формат. Мы показали его на рис. 10.10. Рис. 10.10. Формат регистра состояния современных сопроцессоров В обоих форматах биты 0-5 - флажки особых случаев. Они устанавливаются всегда при возникновении особых случаев, даже замаскированных установкой в 1 соответствующих битов регистра управления. Приведем таблицу флажков особых случаев:
Для того, чтобы сбросить установившийся флажок, программа должна явным образом установить его в нуль, выполнив команду записи в регистр состояния. Назначение бита 7 регистра состояния различно для сопроцессора 8087 и сопроцессоров 80287/80387. Для сопроцессора 8087 этот бит обозначается IR и содержит флаг запроса прерывания при возникновении незамаскированного особого случая. В этом случае флаг устанавливается в 1. Сопроцессоры 80287/80387 используют бит 7 в качестве флага суммарной ошибки, который устанавливается в 1 при возникновении незамаскированного особого случая. Биты C0, C1, C2, C3 - это коды условий. Они определяются по результату выполнения команд сравнения и команды нахождения остатка. Мы расскажем о них при описании соответствующих команд сопроцессора. Поле ST занимает три бита 11-13 и содержит номер численного регистра, являющегося вершиной стека численных регистров. Бит B - бит занятости. Он устанавливается в 1, когда процессор выполняет команду или когда происходит прерывание от сопроцессора. Если сопроцессор свободен, бит занятости установлен в 0. Регистры указателя команды и указателя операндаРегистры указателя команды и указателя операнда предназначены для обработки особых случаев, возникающих при выполнении команд в сопроцессоре. В сопроцессоре 8087 указатель команды содержит 20-разрядный адрес команды, вызвавшей особый случай и код выполняемой в этот момент операции. Адрес команды здесь указывается без учета предшествующих команде префиксов (рис. 10.11). Рис. 10.11. Указание адреса команды в сопроцессоре 8087 Сопроцессоры 80287/80387 в реальном режиме работы имеют такой же формат регистра указателя команд, однако этот указатель показывает на первый префикс команды, вызвавшей особый случай. Защищенный режим работы центрального процессора и сопроцессора описан в 6 томе «Библиотеки системного программиста». Для полноты изложения приведем формат указателей и для этого режима. В защищенном режиме адрес состоит из селектора и смещения. Формат указателя команды для защищенного режима представлен на рисунке 10.12. Рис. 10.12. Формат указателя команды для защищенного режима Код операции здесь отсутствует, но его легко получить, пользуясь адресом команды. Если при возникновении особого случая использовался операнд, находящийся в оперативной памяти, его адрес записывается в регистр указателя операнда. Приведем форматы этого регистра для реального и защищенного режимов работы. Формат указателя операнда для реального режима представлен на рис. 10.13. Рис. 10.13. Формат указателя операнда для реального режима Формат указателя операнда для защищенного режима мы показали на рис. 10.14. Рис. 10.14. Формат указателя операнда для защищенного режима Система команд сопроцессораВозможны три формата команд сопроцессора, аналогичные форматам команд центральных процессоров фирмы Intel. Это команды с обращением к оперативной памяти, команды с обращением к одному из численных регистров и команды без операндов, заданных явным образом. Команды с обращением к памяти могут занимать от двух до четырех байт, в зависимости от способа адресации операнда, находящегося в памяти (рис. 10.15). Рис. 10.15. Формат команд с обращением к памяти Первые пять бит соответствуют команде центрального процессора ESC. Поля КОП1 и КОП2 определяют выполняемую команду, то есть содержат код операции. Поля MOD и R/M вместе с полями "Смещение1" и "Смещение2" задают адрес операнда в памяти аналогично тому, как это происходит в процессорах. Однако есть и отличия, связанные с возможностью адресации численных регистров сопроцессора. Ниже мы покажем зависимость способа адресации от содержимого полей MOD и R/M:
Если в таблице указаны значения смещения disp8 или disp16, это означает, что в команде присуствует один или два байта смещения, соответственно. Если поле MOD содержит значение 11, возможна адресация численных регистров ST0...ST1. При этом команда не содержит байтов смещения. Формат команды с обращением к численному регистру приведен на рис. 10.16. Рис. 10.16. Формат команд с обращением к численному регистру Видно, что это есть частный случай предыдущей команды, в которой поле MOD содержит значение 11 и отсутствуют байты смещения. Самый простой формат имеют команды без явного обращения к операндам (рис. 10.17). Рис. 10.17. Формат команд без явного обращения к операндам Разумеется, если вы составляете программу для сопроцессора на языке ассемблера, вы можете использовать мнемоническое обозначение команд. Все мнемоники команд сопроцесора начинаются с буквы F, поэтому их легко отличить от команд процессоров. Все команды сопроцессора можно разделить на несколько групп: · команды пересылки данных; · арифметические команды; · команды сравнений чисел; · трансцендентные команды; · управляющие команды Команды пересылки данных предназначены для загрузки чисел из оперативной памяти в численные регитры, записи данных из численных регистров в оперативную память, копирования данных из одного численного регистра в другой. Арифметические команды выполняют такие операции, как сложение, вычитание, умножение, деление, извлечение квадратного корня, нахождение частичного остатка, округление и так далее. Команды сравнения сравнивают вещественные и целые числа, выполняют анализ чисел. Трансцендентные команды предназначены для вычисления различных тригонометрических, логорифмических, показательных и гиперболических функций - sin, cos, tg и тому подобных. Последняя группа команд - управляющие команды. Они обеспечивают установку режима работы арифметического сопроцессора, его сброс и инициализацию, перевод сопроцессора в защищенный режим работы и так далее. Следующие разделы будут посвящены детальному описанию различных групп команд сопроцессора. Команды пересылки данныхПриведем описание команд, предназначенных для пересылки данных. Запись в стекFLD ST(0) <- память, вещественный формат FILD ST(0) <- память, целый формат FBLD ST(0) <- память, десятичный формат Команды FLD, FILD, FBLD загружают в вершину стека вещественное, целое и десятичное числа, соответственно. При выполнении этих команд операнд считывается из оперативной памяти, преобразуется в формат с расширенной точностью. Затем поле ST регистра состояния уменьшается на единицу и выполняется запись операнда в численный регистр, определяемый новым значением поля ST. То есть операнд записывается в стек численных регистров, а указатель стека (поле ST) уменьшается на единицу. По своему действию эти команды напоминают команду PUSH центрального процессора. Непосредственно перед загрузкой численного регистра проверяется содержимое поля TAG0. Если это содержимое не равно 11 (пустой регистр), в регистре состояния устанавливается флаг IE (недействительная операция) и вырабатывается прерывание (если в регистре управления не установлена маска IM - маска недействительной операции). Извлечение из стекаFSTP память -> ST(0), вещественный формат FISTP память -> ST(0), целый формат FBSTP память -> ST(0), десятичный формат Команды извлечения чисел из стека выполняют действие, обратное только что описанному. Содержимое численного регистра, номер которого определяется полем ST регистра состояния, преобразуется в необходимый формат и записывается в ячейки оперативной памяти, заданные операндом команды. После записи содержимое поля ST увеличивается на единицу. Эти действия аналогичны выполняемым командой POP центрального процессора. В зависимости от команды (FSTP, FISTP или FBSTP) производится преобразование формата (из расширенного в вещественный, целый или десятичный, соответственно). В процессе преобразования для команд FSTP и FISTP выполняется округление в соответствии с содержимым поля RC регистра управления. Для команды FBSTP округление всегда выполняется следующим образом - прибавляется число 0.5, затем дробная часть результата отбрасывается. Копирование данныхFST память -> ST(0), вещественный формат FIST память -> ST(0), целый формат FBST память -> ST(0), десятичный формат, (только 80387, 80486, Pentium) Эти команды пересылают данные из верхушки стека в область памяти, указанную операндом команды. При этом содержимое указателя стека (поля ST) не изменяется. Команда FST в качестве операнда может использовать ссылку на численный регистр ST(i), поэтому вы можете использовать эту команду для копирования верхушки стека в любой другой численный регистр. При записи данных в оперативную память выполняется преобразование формата (в вещественный для FST, в целый для FIST и в десятичный для FBST. Для сопроцессора 80286 вместо отсутствующей команды FBST можно выполнить следующие две команды, которые приведут к такому же результату: FLD ST(0) FBSTP dec_number ОбменFXCH ST(i) -> ST(0), ST(0) -> ST(i) Команда выполняет обмен содержимым верхушки стека ST(0) и численного регистра, указанного в качестве операнда команды. Загрузка константFLDZ 0 -> ST(0) - Загрузить нуль FLD1 1 -> ST(0) - Загрузить единицу FLDPI p -> ST(0) - Загрузить число p ("пи") FLDL2T loge10 -> ST(0) - Загрузить loge10 FLDL2E log2e -> ST(0) - Загрузить log2e FLDLG2 log102 -> ST(0) - Загрузить log102 FLDLN2 loge2 -> ST(0) - Загрузить loge2 Загрузка констант выполняется намного быстрее специальными командами, нежели командами загрузки данных из оперативной памяти. Арифметические командыСопроцессор использует шесть основных типов арифметических команд:
Строка "xxx" может принимать следующие значения:
Кроме основных арифметических команд имеются дополнительные арифметические команды:
По команде FSQRT вычисленное значение квадратного корня записывается в верхушку стека ST(0). Команда FSCALE изменяет порядок числа, находящегося в ST(0). По этой команде значение порядка числа ST(0) складывается с масштабным коэффициентом, который должен быть предварительно записан в ST(1). Действие этой команды можно представить следующей формулой: ST(0) = ST(0) * 2n, где -215 <= n <= +215 В этой формуле n - это ST(1). Команда FPREM вычисляет остаток от деления делимого ST(0) на делитель ST(1). Знак результата равен знаку ST(0), а сам результат получается в вершине стека ST(0). Действие команды заключается в сдвигах и вычитания, аналогично ручному делению "в столбик". После выполнения команды флаг C2 регистра состояния может принимать следующие значения:
Команда RNDINT округляет ST(0) в соответствии с содержимым поля RC управляющего регистра. Команда FABS вычисляет абсолютное значение ST(0). Аналогично, команда FCHS изменяет знак ST(0) на противоположный. Команды сравнений чиселВ центральном процессоре команды условных переходов выполняются в соответствии с установкой отдельных битов регистра флагов процессора. В арифметическом сопроцессоре существуют специальные команды сравнений, по результатам выполнения которых устанавливаются биты кодов условий в регистре состояния:
Команда FCOM вычитает содержимое операнда, размещенного в оперативной памяти, из верхушки стека ST(0). Результат вычитания никуда не записывается и указатель верхушки стека ST не изменяется. Обозначим операнд команды сравнения как "x". В следующей таблице приведем значения битов кодов условия после выполнения команды "FCOM x":
Последняя комбинация возникает при попытке сравнения нечисел, неопределенностей или бесконечностей, а также в некоторых других случаях. Команда FICOM работает с 16- или 32-разрядными числами, в остальном она аналогична команде FCOM. Команды FCOMP и FICOMP аналогичны, соответственно, командам FCOM и FICOM, за исключением того, что после выполнения операнд извлекается из стека. Команда FCOMPP выполняет те же действия, что и FCOM, но она после выполнения извлекает из стека оба операнда, участвовавших в сравнении. Для сравнения операнда с нулем предназначена команда FTST. После ее выполнения коды условий устанавливаются в соответствии со следующей таблицей:
Команда FXAM анализирует содержимое ST(0). После ее выполнения устанавливаются коды условий, по которым можно судить о знаке числа, о его конечности или бесконечности, нормализованности и так далее. Бит C1 содержит знак анализируемого числа:
С помощью бита C0 можно определить, является число конечным или бесконечным:
Для конечных чисел дальнейшая классификация может проводиться по содержимому кодов условий C2 и C3:
Аналогично, для бесконечных чисел коды условий C2 и C3 имеют следующие значения:
С помощью команды "FSTSW AX" программа может переписать содержимое регистра состояния сопроцессора в регистр AX центрального процессора. Далее содержимое регистра AH можно переписать в регистр флагов центрального процессора при помощи команды SAHF. Биты кодов условий сопроцессора отображаются на регистр флагов центрального процессора таким образом, что для анализа кодов условий можно использовать команды условных переходов. Например, в следующем фрагменте программы выполняется переход к метке error, если операнды несравнимы: .286 . . . fcom fstsw ax sahf je error Трансцендентные командыТрансцендентные команды предназначены для вычисления таких функций, как тригонометрические (sin, cos, tg,...), обратные тригонометрические (arcsin, arccos,...), показательные (xy, 2x, 10x, ex), гиперболические (sh, ch, th,...), обратные гиперболические (arsh, arch, arcth,...). В следующей таблице приведены все трансцендентные команды сопроцессора:
Команда FPTAN вычисляет частичный тангенс ST(0), размещая в стеке такие два числа x и y, что y/x = tg(ST(0)). После выполнения команды число y располагается в ST(0), а число x включается в стек сверху (то есть записывается в ST(1)). Аргумент команды FPTAN должен находится в пределах: 0 <= ST(0) <= pi/4 Пользуясь полученным значением частичного тангенса, можно вычислить другие тригонометрические функции по следующим формулам: sin(z) = 2*(y/x) / (1 + (y/x)2) cos(z) = (1 - (y/x)2) / (1 + (y/x)2) tg(z/2) = y/x; ctg(z/2) = x/y; cosec(z) = (1 + (y/x)2) / 2*(y/x) sec(z) = (1 + (y/x)2) / (1 - (y/x)2) В этой таблице z - значение, находившееся в ST(0) до выполнения команды FPTAN, x и y - значения в регистрах ST(0) и ST(1), соответственно. Команда FPATAN вычисляет частичный арктангенс: z=arctg(ST(0)/ST(1))=arctg(x/y). Перед выполнением команды числа x и y располагаются в ST(0) и ST(1), сответственно. Аргументы команды FPATAN должен находится в пределах: 0 < y < x Результат записывается в ST(0). Команда FYL2X вычисляет выражение y*log2(x), операнды x и y размещаются, соответственно, в ST(0) и ST(1). Операнды извлекаются из стека, а результат записывается в стек. параметр x должен быть положительным числом. Пользуясь результатом выполнения этой команды, можно вычислить следующим образом логарифмические функции: log2(x) = FYL2(x) loge(x) = loge(2) * log2(x) = FYL2X(loge(2), x) = = FYL2X(FLDLN2, x) log2(x) = log10(2) * log2(x) = FYL2X (log10(2), x) = = FYL2X(FLDLG2, x) Функция FYL2XP1 вычисляет выражение y*log2(x+1), где x соответствует ST(0), а y - ST(1). Результат записывается в ST(0), оба операнда выталкиваются из стека и теряются. На операнд x накладывается ограничение: 0 < x < 1 - 1/sqrt(2) Команда F2XM1 вычисляет выражение 2x-1, где x - ST(0). Результат записывается в ST(0), параметр должен находится в следующих пределах: 0 <= x <= 0,5 Команда FCOS вычисляет cos(x). Параметр x должен находится в ST(0), туда же записывается результат выполнения команды. Команда FSIN аналогична команде FCOS, но вычисляет значение косинуса ST(0). Команда FSINCOS вычисляет одновременно значения синуса и косинуса параметра ST(0). Значение синуса записывается в ST(1), косинуса - в ST(0). На этом мы закончим описание трансцендентных команд сопроцессора и перейдем к управляющим командам. Управляющие командыУправляющие команды предназначены для работы с нечисловыми регистрами сопроцессора. Некоторые команды имеют альтернативные варианты. Мнемоники этих команд могут начинаться с FN или с F. Первый вариант соответствует командам "без ожидания". Для таких команд процессор не проверяет, занят ли сопроцессор выполнением команды, то есть бит занятости B не проверяется. Численные особые случаи также игнорируются. Варианты команд "с ожиданием" действуют также, как и обычные команды сопроцессора. Приведем список управляющих команд сопроцессора: FNSTCW (FSTCW) Записать управляющее слово FLDCW Загрузить управляющее слово FNSTSW (FSTSW) Записать слово состояния FNSTSW AX (FSTSW AX) Записать слово состояния в AX, нет в сопроцессоре 8087 FNCLEX (FCLEX) Сбросить особые случаи FNINIT (FINIT) Инициализировать сопроцессор FNSTENV (FSTENV) Записать среду FLDENV Загрузить среду FNSAVE (FSAVE) Записать полное состояние FRSTOR Восстановить полное состояние FINCSTP Увеличить указатель стека на 1 FDECSTP Уменьшить указатель стека на 1 FFREE Освободить регистр FNOP Холостая команда, нет операции FSETPM Установить защищенный режим работы Команда FNSTCW записывает содержимое управляющего регистра в оперативную память. Команда FLDCW загружает управляющий регистр данными из оперативной памяти и обычно используется для изменения режима работы сопроцессора. Команда FNSTSW записывает содержимое регистра состояния в оперативную память. Команда FNSTSW AX записывает содержимое этого регистра в регистр AX центрального процессора для его последующего анализа командами условных переходов. Сопроцессор 8087 не имеет варианта команды FSTSW AX, поэтому приходится вначале записывать регистр состояния в память, а затем в регистр флагов процессора 8086. Команда FNCLEX сбрасывает флаги особых случаев в регистре состояния сопроцессора. Кроме того, сбрасываются биты ES и B. Команда FNINIT инициализирует регистр состояния, управляющий регистр и регистр тегов в соответствии со следующей таблицей:
Команда FNSTENV записывает в память содержимое всех регистров, кроме численных, в формате, показанном на рис. 10.18. Рис. 10.18. Формат записи в память содержимого всех регистров командой FNSTENV Команда FLDENV предназначена для загрузки регистров, сохраненных ранее командой FNSTENV. Обе эти команды полезны в программах обработки особых случаев. Команды FNSAVE и FRSTOR действуют аналогично командам FNSTENV и FLDENV, но они дополнительно сохраняют и восстанавливают содержимое численных регистров. Формат области сохранения регистров, занимающей 94 байта, приведен на рис. 10.19. Рис. 10.19. Формат записи в память содержимого всех регистров командами FNSAVE и FRSTOR Команды FINCSTP и FDECSTP увеличивают и уменьшают на 1 указатель стека SP, соответственно. Команда FFREE ST(i) помечает численный регистр ST(i) как пустой, записывая в соответствующее поле регистра тегов значение 11. Команда FNOP не производит никаких действий. Команда FSETPM переводит сопроцессор в защищенный режим работы. Программирование сопроцессораИспользуя языки высокого уровня, такие как Си или Паскаль, вы можете даже и не знать, что созданная вами программа применяет для вычислений арифметический сопроцессор. В проекте программы вам обычно предоставляется возможность выброа одного из трех вариантов стандартной библиотеки: · библиотека эмулятора; · библиотека, рассчитанная на наличие сопроцессора; · библиотека альтернативной математики Первый вариант (библиотека эмулятора) используется по умолчанию. Программы, которые создаются с использованием эмулятора, будут работать как при наличии в системе сопроцессора, так и при его отсуствии. В последнем случае вычисления с плавающей точкой выполняются специальными подпрограмами, которые присоединяются к вашей программе на этапе редактирования. При запуске ваша программа сама определит факт наличия (или отсуствия) сопроцессора и выберет соответствующий способ выполнения вычислений - либо с использованием сопроцесора, либо с использованием подпрограмм эмуляции сопроцессора. Все что вам нужно для работы с библиотекой эмуляции - просто выбрать ее при установке системы программирования. Это самый простой способ программирования сопроцессора, когда вам, вообще говоря, совсем не надо его программировать - всю работу по использоанию сопроцессора выполнят модули библиотеки эмуляции. Второй вариант библиотеки рассчитан на наличие сопроцессора. Если сопроцессора нет, программа работать не будет. Но если известно, что сопроцессор есть (например, процессор Pentium всегда содержит блок арифметики), то имеет смысл использовать именно этот вариант как самый быстродействующий. Третий вариант не использует сопроцессор совсем. Все вычисления выполняются специальными подпрограммами, входящими в состав библиотеки альтернативной математики и подключающимися к вашей программе автоматически на этапе редактирования. К сожалению, есть программы, в которых использование библиотеки эмуляции невозможно или крайне затруднительно: · резидентные программы; · драйверы; · программы, предъявляющие жесткие требования к точности и скорости вычислений В случае с резидентными программами невозможность использования библиотеки эмулятора вызвана тем, что после оставления программы резидентной в памяти, например, функцией _dos_keep, она теряет доступ к модулям эмуляции. Механизм вызова программ эмуляции основан на использовании прерываний с номерами 34h - 3Eh. Перед тем как оставить программу резидентной, функция _dos_keep восстанавливает содержимое указанных векторов прерываний, делая невозможным доступ резидентной программе к модулям эмулятора. Да и самих этих модулей уже нет в памяти - на их место может быть загружена новая программа. Поэтому в руководстве по языку программирования Си рекомендуется для резидентных программ применять библиотеку альтернативной математики. Но эта библиотека, увы, не использует сопроцессор. Ситуация с драйверами аналогична - драйверы, как правило, составляются на языке ассемблера, поэтому средства эмуляции библиотек Си недоступны. Выходом может быть непосредственное программирование сопроцессора на языке ассемблера. При этом вы можете полностью использовать все возможности сопроцессора и добиться от программы наибольшей эффективности вычислений. Какие средства можно использовать для составления программ для сопроцессора? Обычно это или ассемблер MASM (возможно использование TASM), либо интегрированная среда разработки, содержащая встроенный ассемблер. Программа NPU1Приведем пример самой простой программы NPU1 (листинг 10.1), которая выполняет вычисления по следующей несложной формуле: z = x + y; В этой программе значения x и y задаются в виде констант. Листинг 10.1. Файл npu1\npu1.asm ; ===================================================== ; Простейшая программа для работы с арифметическим ; сопроцессором ; ; (C) A. Frolov, 1997 ; ; E-mail: frolov@glas.apc.org ; WWW: http://www.glasnet.ru/~frolov ; or ; http://www.dials.ccas.ru/frolov ; ===================================================== .model small .STACK 100h .DATA ; Здесь находятся константы с одинарной ; точностью x и y x dd 1.0 y dd 2.0 ; Резервируем четыре байта для результата z dd ? .CODE begin: mov ax, DGROUP mov ds, ax ; Записываем в стек численных регистров ; значение x fld x ; Складываем содержимое верхушки стека ; с константой y fadd y ; Записываем результат в ячейку z fstp z ; Завершаем работу программы и ; возвращаем управление операционной системе mov ax, 4C00h int 21h END begin Как убедиться в том, что программа работает правильно? Для этого мы используем отладчик CodeView, содержащий очень удобные средства отладки программ, работающих с арифметическим сопроцессором. Запустим отладчик CodeView, передав ему в качестве параметра имя приведенной выше программы: cv npu1.exe После того, как отладчик запустится, откройте окно регистров сопроцессора. В нижней части экрана появится окно регистров сопроцессора, показанное на рис. 10.20. Рис.10.20. Окно регистров сопроцессора Пусть вас не смущает то, что в этом окне пока не показывается состояние регистров сопроцессора. Нажмите клавишу F8, выполнив один шаг программы. Теперь вы видите содержимое регистров управления и состояния (cControl, cStatus), регистра тегов (cTag), регистров указателей команд и данных (Instr Ptr, Data Ptr), код выполняемой команды (Opcode). Отображается также содержимое стека численных регистров (Stack), но пока это поле пустое, так как все численные регистры отмечены в регистре тегов как пустые. Нажмите еще раз клавишу F8, выполнив следующую команду программы. Эта команда запишет в стек численных регистров значение переменной x. Теперь в области регистров стека показано содержимое регистра cST(0), причем как в двоичном виде, так и с использованием экспоненциальной (научной) нотации. Как и следовало ожидать, регистр ST(0) содержит величину 1.0 (рис. 10.21). Рис. 10.21. В регистре ST(0) находится значение 1.0 Выполним еще одну команду, прибавляющую к содержимому ST(0) значение 2.0 из переменной y. Теперь регистр ST(0) содержит величину 3.0 (рис. 10.22). Рис. 10.22. Теперь в регистре ST(0) находится значение 3.0 Последняя команда выталкивает из стека хранящееся там значение (3.0) и записывает его в переменную z. Теперь стек численных регистров снова пуст. Обработка особых случаевВ арифметическом сопроцессоре имеются два механизма обработки ошибок, возникающих при выполнении различных команд. Первый механизм основан на генерации так называемого прерывания особого случая (INT 10h). Это прерывание вырабатывается в том случае, когда происходит какая-нибудь ошибка (например, деление на нуль) при условии, что соответствующие биты масок особых случаев в регистре управления не установлены. При втором способе обработки ошибок все особые случаи маскируются (соответствующие биты управляющего регистра устанавливаются в единицу) и в случае ошибки сопроцессор в качестве результата возвращает некоторое заранее известное особое значение (нечисло, неопределенность или бесконечность). Программист может выбирать между этими способами обработки ошибок, маскируя или разрешая прерывание по особому случаю. Если прерывание особого случая замаскировано, можно предложить следующий способ обнаружения ошибки: · сбросить флажки особых случаев в регистре сосотояния; · выполнить одну или несколько команд сопроцессора; · проверить состояние флажков особых случаев в регистре состояния, в частности, бит суммарной ошибки ES; · если какой-либо флажок установлен, вызвать программу обработки ошибочной ситуации; · в программе обработки ошибочной ситуации можно сбросить флажки особых случаев, записав соответствующее значение в регистр состояния Кроме того, после выполнения команды полезно проверить получившийся результат на принадлежность к множеству особых значений. Рассмотрим возможные особые случаи сопроцессора в реальном режиме. Неточный результатВ результате выполнения некоторых операций может возникнуть такая ситуация, когда невозможно точно представить результат. Например, при результатом деления числа 1.0 на 3.0 является бесконечная периодическая двоичная дробь 0.010101... Такое число не может быть представлено точно ни в одном формате вещественных чисел. Обычно неточный результат является результатом округления и может не рассматриваться как ошибка. ПереполнениеЕсли результат выполнения операции слишком велик и не может быть представлен в формате приемника результата, фиксируется особый случай переполнения. Этот особый случай обязательно произойдет, например, при сложении максимального числа расширенной точности самим с собой или при преобразовании этого числа в формат с двойной или одинарной точностью. Так как для хранения промежуточных результатов используется 80-битовое представление, при выполнении операций над числами с одинарной или двойной точностью переполнения, как правило, не происходит. Огромный диапазон чисел с расширенной точностью гарантирует правильность представления больших по абсолютной величине результатов операций с числами одинарной и двойной точности. АнтипереполнениеАнтипереполнение возникает тогда, когда результат слишком мал для его представления в формате приемника результата операции, но все же отличен от нуля. Например, если делается попытка преобразовать наименьшее положительное число с расширенной точностью в формат числа с двойной или одинарной точностью. Если вы используете числа только с двойной или одинарной точностью, а для хранения промежуточных результатов используете формат с расширенной точностью, особый случай антипереполнения, как правило, не возникает. Деление на нульЭтот особый случай возникает при попытке выполнить деление конечного ненулевого числа на нуль. В афинном режиме при делении конечных (положительных или отрицательных) чисел на нуль (положительный или отрицательный) в качестве результата возвращается бесконечность. Знак этой бесконечности зависит от знака делимого и от знака нуля. Например, при делении положительного ненулевого числа на положительный нуль получается положительная бесконечность, при делении положительного ненулевого числа на отрицательный нуль - отрицательная бесконечность. В проективном режиме, а также при попытке деления нуля на нуль возникает особый случай недействительной операции, который будет рассмотрен ниже. Недействительная операцияЭтот особый случай возникает при попытке выполнения таких запрещенных команд, как деление нуля на нуль, извлечения корня из отрицательного числа, обращение к несуществующему регистру сопроцессора или при попытке использования в качестве операндов команд нечисел, неопределенностей или бесконечности (для трансцендентных функций). Денормализованный операндМы уже говорили о том, что сопроцессор использует операнды в нормализованной форме. Однако при выполнении операции может оказаться, что результат слишком мал по абсолютной величине для представления его в нормализованной форме. Можно было бы считать такой результат нулевым, однако это привело бы к снижению точности вычислений или даже к грубым ошибкам. Например, вычисляется следующее выражение: (y-x)+x; Если разность (y-x) вызывает антипереполнение и в качестве результата берется нулевое значение, то после вычисления всего выражения получится x. Если же пойти на расширение диапазона представления чисел за счет снижения точности и сформировать результат вычисления разности (y-x) как денормализованное число, выражение будет вычислено правильно и в результате получится y. Таким образом, иногда целесообразно замаскировать особый случай денормализованного операнда и использовать денормализованные числа. Однако при попытке деления на ненормализованное число или извлечения из него квадратного корня фиксируется особый случай недействительной операции. Ошибка в процессоре PentiumВ листинге 10.2 мы привели исходный текст программы PENTERR, с помощью которой можно обнаружить известную ошибку в арифметическом сопроцессоре, встроенном в процессор Pentium. Эта ошибка приводит к снижению точности результата при делении некоторых комбинаций чисел. В нашем случае мы проверяем результат вычислений по следующей формуле: (4195835 / 3145727) * 3145727 Если ошибки нет, в результате вычислений должно получиться значение 4195835. При наличии ошибки значение результата будет меньше этого числа на 256. Листинг 10.2. Файл penterr\penterr.asm ; ===================================================== ; Проверка наличия ошибки в сопроцессоре, ; встроенном в процессор Pentium ; ; (C) A. Frolov, 1997 ; ; E-mail: frolov@glas.apc.org ; WWW: http://www.glasnet.ru/~frolov ; or ; http://www.dials.ccas.ru/frolov ; ===================================================== .model small .STACK 100h .DATA .286 x dd 4195835.0 y dd 3145727.0 z dd ? BugMsg db 13,10,"Pentium NPU bug detected!", "$" OkMsg db 13,10,"Your Pentium NPU is OK", "$" .CODE begin: mov ax, DGROUP mov ds, ax ; Записываем в стек численных регистров ; значение x fld x ; Делим содержимое верхушки стека ; на константу y fdiv y ; Умножаем содержимое верхушки стека ; на эту же константу fmul y ; В результате при отсутствии ошибки мы должны ; получить результат, равный x fcom x ; Сохраняем регистр состояния сопроцессора в AX fstsw ax ; Переписываем AH в регистр флагов sahf ; Проверяем равенство нулю jnz bug ; Ошибки нет mov ah, 9 mov dx, offset OkMsg int 21h jmp next ; Обнаружена ошибка bug: mov ah, 9 mov dx, offset BugMsg int 21h ; Завершаем работу программы и ; возвращаем управление операционной системе next: mov ax, 4C00h int 21h END begin |