В гл. 6 рассматривались представление целых чисел и арифметические операции над этими числами. Представление целых чисел является примером представления чисел с фиксированной точкой, так как подразумевается, что десятичная точка находится на строго определённом месте. Если целое число содержится в 32-битовом длинном слове, то подразумевается, что десятичная точка находится справа от крайнего правого бита длинного слова. Хотя представление чисел с фиксированной точкой пригодно для многих приложений, оно является неудобным для тех задач, в которых приходится иметь дело как с очень малыми дробными, так и с очень большими значениями. Таких задач существует много в науке и технике, а также в других областях.
Представление чисел с плавающей точкой было разработано для эффективного представления как дробных, так и очень больших значений чисел. Представление чисел с плавающей точкой сходно с экспоненциальным форматом записи чисел в том, что числа представляются как последовательности значащих цифр, умноженных на какое-либо число, возведённое в степень.
В этой главе читатель познакомится с тем, как вообще осуществляется обработка дробных значений и как это делается, в частности, на ЭВМ семейства VAX. Будут изучены различные представления чисел с плавающей точкой, применяемые в ЭВМ семейства VAX, и инструкции для работы с этими числами.
Для того чтобы эффективно использовать числа с фиксированной и с плавающей точкой, важно понять принципы работы с этими числами. Чтобы упростить изложение, числа с фиксированной и плавающей точкой будут рассматриваться на основе десятичного представления. После этого будет рассмотрено представление этих чисел в ЭВМ семейства VAX и показано, какие искусные приёмы применяются для того, чтобы такое представление было эффективным.
Существуют разнообразные способы выражения дробных значений. Наиболее общий способ основывается на использовании пар целых чисел, таких как 1/2, 5/12 или 537/8946. Этот способ представления дробей довольно легко может быть реализован на ЭВМ. Однако обычно он не применяется в вычислениях из-за свойственного ему неудобства. Вместо него более предпочтительным является ограничение дроби множеством стандартных знаменателей, таких как десятки, сотни, тысячи и т.д. Поскольку эти знаменатели представляют собой степени числа 10, образованные таким образом дроби называются десятичными. Обычно они изображаются с десятичной точкой или запятой в позиционном представлении чисел, например 5.4, 7.2 или 0.093. Существуют также и другие системы построения дробей, в которых используются знаменатели, такие как 12, 14, 16, 32 и 60. И хотя некоторые из них вышли из употребления с введением метрической системы, оставшиеся ещё находят, применение. В большинстве ЭВМ используются или десятичные, или двоичные дроби (которые будут рассмотрены позже).
Для работы с нецелочисленными значениями в ЭВМ широко применяются два способа представления чисел: с фиксированной и с плавающей точкой. Наиболее простым из них является представление чисел с фиксированной точкой. Этот способ представления основан на том, что часто имеется такая наименьшая дробь, которая достаточна для представления наименьшего значения и при которой не надо рассматривать ещё какие-либо меньшие значения. Хорошей иллюстрацией этого являются финансовые расчёты. В большинстве финансовых расчётов в США используется такая денежная единица, как доллар, а в качестве наименьшей единицы рассматривается один цент, или 1/100 долл.[1].
Поскольку нет нужды иметь дело с долями центов, в постановке задачи можно было бы перейти к целому числу центов. Например, 537.23 долл. - это то же самое, что 53 723 центов. Однако, поскольку большинство людей путаются, когда им приходится иметь дело с большим числом центов, а не долларов, более предпочтительно о том же самом говорить так, что число 53 723 имеет подразумеваемую десятичную точку, отделяющую две позиции справа. Поэтому это число представляет 537.23 долл. Это называется представлением чисел с фиксированной точкой, так как десятичная точка подразумевается на некотором фиксированном месте в числе.
Представление чисел с фиксированной точкой очень удобно для финансовых расчётов; именно поэтому оно широко используется в предназначенных для экономических приложений языках, таких, например, как Кобол. Числа с фиксированной точкой могут также применяться в научных расчётах, при приведении данных к соответствующей шкале единиц. Поэтому в научных задачах очень часто используются целочисленные значения, такие, как миллиамперы, микросекунды, сантиметры, килограммы, тонны. Последние два элемента списка представляют вариацию описанной ранее проблемы долларов и центов, поскольку десятичная точка эффективно переносится вправо, за пределы числа. Следовательно, вместо подсчёта долей некоторой единицы можно подсчитать значения, кратные какой-либо единице. Иногда это необходимо, чтобы избежать вычисления с многократно увеличенной точностью, к которым приходится прибегать при работе с очень большими значениями. Например, если вес супертанкера выражать в миллиграммах, то потребовались бы многозначные числа. Следовательно, должны использоваться тонны или даже килотонны.
Проблема приведения величин к соответствующей шкале единиц может быть довольно сложной и была фактически одной из самых трудных при программировании на некоторых первых моделях ЭВМ. Трудность заключается в том, что во многих научных задачах приходится иметь дело как с очень большими, так и с очень маленькими числами. Например, при рассмотрении корабля с ядерной силовой установкой могут использоваться килотонны в отношении массы корабля и миллиграммы в отношении массы гранул топлива, так что единицы массы в рамках одной задачи оказываются несогласованными.
Решение вышеназванной проблемы состоит в использовании чисел с плавающей точкой, Число с плавающей точкой в действительности состоит из двух частей. Одна часть содержит знак и цифры числа, другая часть показывает, в каком месте предполагается нахождение десятичной точки. Аналогичный способ часто используется учёными для того, чтобы их вычисления могли выполняться в стандартных единицах. В применяемой ими записи числа представляются в виде нескольких значащих цифр, умноженных на некоторую степень числа 10. Например,
-5.347 × 1015 = -5347000000000000
или
4.92 × 10-9 = 0.000000004912
В действительности это есть ±а × 10±b, где а представляет значащие цифры числа, а b представляет степень числа 10 - порядок числа. Обратите внимание, что оба числа, как a, так и b, имеют знак.
При представлении в ЭВМ чисел с плавающей точкой требуется где-то хранить два числа со знаком. Это ±а и ±b. Требуется также понять, что эти числа означают. Хотя оба числа можно хранить в двух отдельных ячейках памяти, это неэффективно, поскольку b имеет тенденцию быть относительно небольшим числом, и было бы расточительно отводить для хранения этих чисел одинаковый объём памяти. Вместо этого обычно используют некоторые средства для совместной упаковки обоих чисел. Предположим, что имеется вычислительная машина, слово которой вмещает 8-разрядное число со знаком, например +73214694 (напомним, что временно используется десятичное представление).
Хорошим компромиссом для упаковки чисел а и b в это слово было бы отвести два разряда слова для b и остальные шесть разрядов - для а. Это даёт шесть значащих цифр в числе и диапазон значений, определяемый множителем 1099. Таким образом, если в машинном слове содержится значение +51314159, то его часть +314159 соответствует а, а 51 соответствует b. Возникают два вопроса. Первый: "Где в числе а подразумевается положение десятичной точки?" Хотя положение точки может быть произвольным, большинство производителей ЭВМ помещают её в самой левой позиции. Следовательно, а = +0.314159. Второй вопрос: "Что произошло со знаком для числа b?" Машинное слово имеет только один знаковый разряд, который использовался для знака числа а. Однако, если требуется представлять и малые, и большие числа, необходимо иметь знак также и для числа b.
Обычный метод состоит в том, чтобы в двух разрядах, отведённых для представления b, хранить число, которое больше действительного значения b на некоторое строго определённое значение. Если такое фиксированное значение равно 50, то заданное в этих разрядах число 50 означает, что число b равно 0, число 51 означает, что число b равно 1, число 52 означает b = 2, и т.д. Числа, меньшие 50, служат для представления отрицательных порядков. Например, число 49 означает, что b равно -1, а число 41 означает, что b равно -9. Поскольку число 51 соответствует b, равному 1, содержимое слова +51314159 представляет число +0.314159 × 101 - или просто 3.14159. Чтобы отличить два представления порядка, значение b будем называть истинным значением порядка, а значение b + 50 - порядком с избытком 50 или просто порядком с избытком. На рис. 12.1 показаны примеры чисел с плавающей точкой и их эквиваленты.
Число с плавающей точкой | Экспоненциальная форма | Десятичное представление |
---|---|---|
-46134926 |
-0.134926 × 10-4 |
-0.0000134926 |
+50934821 |
+0.934821 × 100 |
0.934821 |
+50999999 |
+0.999999 × 100 |
0.999999 |
+51100000 |
+0.100000 × 101 |
1.00000 |
-53426910 |
-0.426910 × 103 |
-426.910 |
Рис. 12.1. Представление десятичных дробей
Как видно из рис. 12.1, имеются три явно выраженных части слова с плавающей точкой: знаковый разряд для а; разряды, в которых представлен порядок b; разряды, отведённые для представления значения a. Последние обычно называют дробной частью или мантиссой вследствие подразумеваемого положения десятичной точки в а (как отмечалось выше, разряды, в которых представлено значение b, называют порядком с избытком). На рис. 12.2 показаны составные части формата числа с плавающей точкой.
Знак |
Порядок |
Мантисса |
Рис. 12.2. Формат числа с плавающей точкой
Последнее замечание по поводу представления чисел с плавающей точкой касается нормализации. Из предыдущего обсуждения можно видеть, что содержимое слова +51100000 представляет собой 0.1 × 101 = 1. Аналогично +54000100 представляет - 0.0001 × 104 = 1. Следовательно, в любом случае +51100000 и +54000100 представляют одно и то же число. Чтобы предотвратить возможную путаницу в большинстве систем представления чисел с плавающей точкой, требуется, чтобы число было приведено к такому виду, при котором самый левый разряд мантиссы не был бы равен 0 (как в +51100000). Это называют нормализованным представлением числа с плавающей точкой. Такое требование помимо устранения путаницы важно прежде всего потому, что нормализованные числа с плавающей точкой сохраняют максимальное число значащих цифр. При работе с ненормализованными числами, такими как +54000100, может теряться точность. Одно исключение из правила нормализации касается числа 0, которое имеет нормализованное представление +00000000.
Посмотрим теперь, какой диапазон значений возможен при таком представлении чисел с плавающей точкой. Диапазон возможных значений приведён на рис. 12.3. Обратите внимание, что существует разрыв между значениями 10-51 и 0. Это означает, что следует избегать употребления слишком маленьких чисел, так как информационной ёмкости слова может оказаться недостаточно для их представления.
Наименьшее число | -99999999 = |
-0.999999 |
× |
1049 ≈ |
-1049 |
Наибольшее отрицательное число | -00100000 = |
-0.100000 |
× |
10-50 = |
-10-51 |
Нуль | +00000000 = |
0.000000 |
× |
100 = |
0 |
Наименьшее положительное число | +00100000 = |
0.100000 |
× |
10-50 = |
+10-51 |
Наибольшее число | +99999999 = |
0.999999 |
× |
1049≈ |
+1049 |
Рис. 12.3. Диапазон нормализованных десятичных дробей
Сама по себе возможность представления чисел в формате с плавающей точкой по сути бесполезна, если только не существует какого-либо способа выполнения операций над такими числами. На ЭВМ обычно можно выполнять операции сложения, вычитания, умножения и деления (другие математические операции и функции являются производными от этих четырёх). В этом разделе будет показано на примере десятичных чисел, как эти операции могут выполняться над числами с плавающей точкой, представление которых было описано в предыдущем разделе.
Сначала рассмотрим сложение и вычитание (эти операции очень близки и различаются только тем, как обрабатывается знак числа). Рассмотренное в предыдущем разделе правило гласит, что первый шаг сложения десятичных чисел состоит в выравнивании положения точек. Таким образом,
573.426 +8.93425 |
|
573.426 |
После этого выполняется простое сложение значений каждого разряда чисел. Аналогичное правило применимо к числам, представленным как в экспоненциальной форме, так и с плавающей точкой. Представленные в такой форме два числа нельзя складывать до тех пор, пока не будут равны их порядки. Следовательно, для аналогичного примера
.573426 × 103 |
|
.573426 × 103 |
Проследим теперь шаг за шагом обработку тех же самых чисел, представленных в формате с плавающей точкой, и посмотрим, как такая обработка может быть осуществлена в ЭВМ.
Шаг 1. Выровнять два числа одно под другим:
+53573426 |
+51893425 |
Шаг 2. Распаковать числа, чтобы отделить друг от друга мантиссу и порядок:
53 |
+573426 |
51 |
+893425 |
Шаг 3. Расположить числа так, чтобы первым было число с наибольшим порядком, переставив числа, если необходимо:
53 |
+573426 |
51 |
+893425 |
Шаг 4. Вычислить разность порядков:
53 |
+573426 |
-51 |
+893425 |
2 |
|
Шаг 5. Сдвинуть второе число вправо на число разрядов, равное вычисленной разности, и сделать порядки чисел равными:
53 |
+573426 |
|
53 |
+008934 |
25[2] |
Шаг 6. Сложить мантиссы. Порядок результата остаётся тем же самым:
53 |
+573426 |
53 |
+008934 |
53 |
+582360 |
Шаг 7. Упаковать результат в формат с плавающей точкой:
+53582360
Результат сложения равен 582.36.
Алгоритм, рассмотренный выше, без сомнения правильно работает для приведённого примера. Однако могут возникнуть две сложности, которые требуют двух дополнительных шагов. Первая проблема состоит в том, что сумма двух чисел может занимать больше разрядов, чем имеется у первоначальных чисел. Например, предположим, что складываются числа в формате с плавающей точкой +53573426 и 53698421. После выполнения шагов 1 - 5 обнаруживается, что сдвиг не нужен, и будет получено
53 |
+573426 |
53 |
+698421 |
Теперь перейдём к шагу 6
53 |
+573426 |
53 |
+698421 |
|
+1271847 |
Отметим, что результирующая дробная часть числа занимает более шести разрядов, а это не позволит упаковать его в восьмиразрядное слово. Решение состоит в том, чтобы сдвинуть мантиссу на один разряд вправо и прибавить единицу к порядку числа:
Шаг 6а.
54 |
+127184 |
7[3] |
Шаг 7а. Упаковать результат в формат с плавающей точкой.
+54127184
Вторая проблема в некотором смысле противоположна первой. Она возникает при вычитании чисел с одинаковыми знаками или при сложении чисел с противоположными знаками. Опять шаги 1 - 5 - те же самые. Но при выполнении вычитания на шаге 6 результат может занимать менее шести разрядов. Например, если складываются числа +53573426 и -53573213, то на шаге 6 будет следующее:
53 |
+573426 |
53 |
-573213 |
53 |
+000213 |
Отметим, что если упаковать это число, результат окажется ненормализованным. Поэтому необходимо нормализовать результат и получить шаг 6б.
Шаг 6б.
50 |
+213000 |
Заметим, что нули справа означают потерю точности. Это обычно происходит при вычитании очень близких по значению чисел. На шаге 6б нужно иметь в виду, что результат может быть нулевым. В этом случае при нормализации будет получено
00 |
+000000 |
Правила умножения и деления чисел с плавающей точкой прямо вытекают из правил, применяемых к числам в экспоненциальной форме. При умножении порядки складываются, при делении порядки вычитаются. Дробные части чисел или умножаются, или делятся. Например,
(0.5 × 1015) × (0.8 × 104) = (0.5 × 0.8) × 1015 + 4 = 0.4 × 1019.
Аналогично
(0.4 × 1019) ÷ (0.5 × 1015) = (0.4 ÷ 0.5) × 1019 - 15 = 0.8 × 104.
Правила умножения чисел с плавающей точкой представлены ниже:
Шаг 1. Расположить два числа одно под другим:
+65500000 |
+54800000 |
Шаг 2. Распаковать числа, как при сложении:
65 |
+500000 |
54 |
+800000 |
Шаг 3. Теперь умножаются мантиссы. Для мантиссы или порядков никакой подстройки не требуется (замечаем, где в результате оказывается десятичная точка, т. е. 0.50 × 0.80 равно 0.4000):
65 |
+0.500000 |
54 × |
+0.800000 |
|
+0.400000 |
Шаг 4. Складываются порядки. Но каждый порядок имеет избыток 50, так что результат будет иметь избыток 100. Следовательно, необходимо вычесть 50:
65 |
|
Шаг 5. Теперь нужно упаковать результат.
+69400000
Поскольку дробные части всегда меньше 1, произведение двух мантисс должно быть меньше 1. Следовательно, проблема, которая существовала при сложении на шаге 6а, здесь не возникает. Однако произведение может быть меньше чем 0.1, поэтому иногда необходима нормализация. Она выполняется так же, как на шаге 6б, при сложении. Например, если число +51150000 умножается на +52200000, то будут выполнены
Шаги 3 и 4:
51 + |
+0.150000 |
(заметим, что 0.15 × 0.20 равно 0.0300) |
Необходима нормализация, чтобы получить
Шаг 4а:
52 |
+300000 |
Это даёт результат, равный +52300000. Обратите внимание, что в примере фактически выполняется - 1.5 × 20 равно 30.
Операция деления в существенной степени аналогична операции умножения. Подробности выполнения операции деления читателю предлагается разобрать самостоятельно в качестве упражнения.
Представление чисел с плавающей точкой, рассмотренное выше, может показаться неудобным. Слева направо оно состоит из разряда знака мантиссы, двух разрядов порядка, представленного с избытком 50, и шестиразрядной мантиссы. Такой формат был выбран, чтобы упростить сравнение двух чисел с плавающей точкой.
Предположим, что желательно определить, больше ли число с плавающей точкой X, чем число с плавающей точкой Y. Это можно сделать, выполнив вычитание этих чисел Y - X и проверив, отрицателен ли результат. Однако существует более быстрый и лёгкий способ. Нужно рассмотреть представления чисел X и Y, как если бы они были восьмиразрядными целыми со знаком, и вычислить Y - X, используя целочисленную арифметику. Если полученное в результате целое число отрицательно, то X (как число с плавающей точкой) больше, чем Y. Например, предположим, что X равно 10.0, a Y равно 1.0. Представление этих чисел в формате с плавающей точкой для X и Y будет соответственно +52100000 и +51100000. Вычитание +52 100 000 из +51 100 000 как целых со знаком даст -01 000 000, а это показывает, что Х(10.0) больше, чем Y (1.0) (в данном случае оба числа X и Y положительны и порядок числа X превосходит порядок числа Y на 1, так что X больше).
Предлагаем читателю проверить справедливость этого положения для любой пары чисел с плавающей точкой при условии, что числа нормализованы. Чтобы такой краткий вариант сравнения работал, порядок должен располагаться между знаком мантиссы и самой мантиссой. Этим объясняется и использование представления порядка с избытком (в данном случае 50). На ЭВМ, не имеющих инструкций для сравнения чисел с плавающей точкой, варианты этого краткого способа используются для создания эффективного программного обеспечения для сравнения чисел с плавающей точкой. На ЭВМ, имеющих такие инструкции, этот способ может использоваться для упрощения конструкции аппаратуры.
В процессе вычислений с плавающей точкой могут возникнуть различные ошибки. Например, попытка разделить число с плавающей точкой на 0 приводит к ошибке деления на нуль числа с плавающей точкой. Возможно также, что операции над числами с плавающей точкой могут приводить к переполнению числа с плавающей точкой. Как показано на рис. 12.3, в приведённом формате с плавающей точкой могут быть представлены только числа от -10-49 до 1049. Попытка образовать число, не попадающее в этот диапазон, приводит к переполнению числа с плавающей точкой. Рассмотрим, например, следующее умножение:
+98800000 × +54400000
Мантисса результата - это .800000, умноженное на .400000, или .320000. Порядок с избытком 50 равен 98 плюс 54 минус 50 или 102. Правильный результат умножения есть .32 × 1052 (порядок с избытком 50, равный 102, представляет истинный порядок 52). Поскольку порядок с избытком 50 больше 99, он не может уместиться в двухразрядном поле порядка и происходит переполнение числа с плавающей точкой.
Рассмотрим следующее умножение:
+02800000 × +40400000
Мантисса результата - это опять .800000, умноженное на .400000, или .320000. Однако порядок с избытком 50 есть 02 плюс 40 минус 50 или -08. Правильным результатом умножения будет .32 × 10-58 (порядок -08 с избытком 50 представляет истинный порядок -58). Поскольку порядок с избытком 50 меньше 0, в двухразрядном поле порядок не умещается. Такая ошибка называется потерей значимости числа с плавающей точкой.
Обратите внимание, что переполнение и потеря значимости числа с плавающей точкой весьма отличаются друг от друга. Переполнение числа с плавающей точкой означает, что абсолютное значение результата операции над числами с плавающей точкой слишком велико и не может быть представлено в данной разрядной сетке. Потеря значимости числа с плавающей точкой означает, что результат слишком близок к нулю и поэтому также не может быть представлен. Потеря значимости может происходить при вычитании двух небольших, очень близких по значению чисел, при умножении двух небольших по значению чисел или при делении небольшого числа на большое число.
В описанном представлении чисел с плавающей точкой порядок 00 с избытком 50 показывает, что истинный порядок есть -50. Например, число в формате с плавающей точкой
+00123456
представляет число .123456 × 10-50 Нулевой порядок не имеет какого-то специального значения. Представление чисел с плавающей точкой, используемое на целом ряде ЭВМ, следует этому соглашению. Однако на других ЭВМ, куда входят и ЭВМ семейств VAX и PDP-11, нулевой порядок рассматривается как некий специальный случай. В ЭВМ семейства VAX положительная мантисса при нулевом порядке интерпретируется как значение 0 независимо от действительного значения мантиссы. То есть любое число с плавающей точкой, аналогичное следующему:
+00??????
интерпретируется как 0. Причина, по которой это сделано именно так, станет ясной из описания различных форматов чисел с плавающей точкой, используемых на ЭВМ семейства VAX.
Когда нулевой порядок рассматривается таким образом, возникает вопрос о том, как интерпретировать числа с плавающей точкой, аналогичные следующему:
-00??????
Такие числа могли бы интерпретироваться как отрицательный 0. Но иметь два варианта 0 неудобно и это может приводить к ошибкам программирования. Вместо этого на ЭВМ семейства VAX такие числа рассматриваются как резервные операнды. Это попросту означает, что числа, аналогичные -00??????, рассматриваются как недопустимые числа с плавающей точкой, а попытка использования таких чисел в операциях над числами с плавающей точкой приводит к возникновению ошибки. Резервный операнд может оказаться и довольно полезным. Например, транслятор языка высокого уровня может инициализировать все переменные, имеющие формат числа с плавающей точкой, недопустимыми значениями. Если во время выполнения программа пытается обратиться к переменной с плавающей точкой без предварительной её инициализации, то это немедленно вызовет ошибку по использованию резервного операнда.
а. | 5 |
6. | 374 |
в. | 3.14159 |
г. | 0.0005 |
д. | 0.8035 × 1023 |
е. | 0.4923 × 10-15 |
ж. | 8.496 × 1018 |
з. | 954.2 × 10-12 |
а. | +51300000 |
б. | -53742000 |
в. | +50894026 |
г. | +45805216 |
д. | -56293465 |
е. | -57100000 |
ж. | +38950125 |
з. | -64790881 |
а. | (+53215904) + (+53116895) |
б. | (+52159099) + (+49889621) |
в. | (+50912065) - (+54891126) |
г. | (-52998046) + (-50479138) |
д. | (-53885304) - (-53885034) |
е. | (+57900000) × (+48800000) |
ж. | (+51426931) × (-44357926) |
з. | (-43250000) × (-41250000) |
и. | (-55255000) ÷ (+51500000) |
к. | (-41800000) ÷ (-44200000) |
XНОВ = 0.5 × (XСТАР + N/XСТАР).
Новое значение X будет много ближе к правильному корню квадратному, чем старое значение. Формула может применяться повторно для получения ответа желаемой точности. Скорость сходимости метода зависит от числа повторных вычислений по формуле, а оно, в свою очередь, зависит от того, насколько близко значение первоначально заданного числа к искомому результату, а также от желаемой точности результата:
Операции над числами с плавающей точкой в ЭВМ семейства VAX осуществляются во многом так же, как и с числами, описанными в предыдущем разделе. Однако в ЭВМ VAX числа с плавающей точкой кодируются в двоичном, а не десятичном виде. Это означает, что мантисса выражается как двоичное число и что порядок есть степень числа 2, а не степень числа 10.
Вспомним из гл. 2, что двоичное число 11011 интерпретируется так:
11011 |
= |
(1 × 24) |
+ |
(1 × 23) |
+ |
(0 × 22) |
+ |
(1 × 21) |
+ |
(1 × 20) |
= |
|
= |
16 |
+ |
8 |
+ |
0 |
+ |
2 |
+ |
1 |
= |
|
= |
27. |
|
|
|
|
|
|
|
|
|
Двоичные дроби обрабатываются аналогично, но с отрицательными порядками. Следовательно, двоичная дробь 0.100011 интерпретируется так:
0.100011 | = (1 × 2-1) + (0 × 2-2) + (0 × 2-3) + (0 × 2-4) + (1 × 2-5) + (1 × 2-6) = |
= 1/2 + 0 + 0 + 0 + 1/32 + 1/64 = 35/64. |
Двоичные дроби могут показаться странными, но фактически они используются как обычно. Дюймы, принятые в английской системе измерений, обычно делятся на двоичные доли. Нет ничего непривычного в том, что механик может пользоваться сверлом диаметром 35/64 дюйма. В большинство домашних наборов плотничьего инструмента входят сверла диаметром от 1/64 до 1/4 дюйма.
Следует отметить, что не все дроби можно точно выразить в виде двоичных дробей. И такая ситуация знакома по десятичным дробям. Известно, что одна треть не может быть выражена в десятичном виде. Лучшее, что можно сделать, это написать что-то вроде 0.333333, но такая запись неточна. Можно улучшить её, добавив тройки, но никакое конечное число троек не сделает число точным. Как и следовало ожидать, одну треть невозможно точно выразить и в двоичном виде. Но более удивительно то, что невозможно точно выразить в двоичном виде и дробь 1/5. В десятичном виде можно записать 1/5 = 0.2, но нельзя сделать так же в двоичном виде. Следующая таблица показывает, как можно приблизительно выразить дробь 1/5 двоичными дробями, но она никогда не будет равна ни одной из них:
1/4 |
> |
1/5 |
> |
1/8 |
1/4 |
> |
1/5 |
> |
3/16 |
7/32 |
> |
1/5 |
> |
3/16 |
13/64 |
> |
1/5 |
> |
3/16 |
13/64 |
> |
1/5 |
> |
25/128 |
Как и в случае десятичных чисел, двоичные числа с плавающей точкой имеют порядок и мантиссу. Посмотрим, как эти части числа размещаются в различных форматах. В ЭВМ семейства VAX применяются четыре формата с плавающей точкой: формат типа F, формат типа D, формат типа G и формат типа Н. Требуемый для них объём памяти изменяется от 32-битового длинного слова (формат F) до 128-битового октаслова (формат Н). Кроме того, форматы типов D и G, каждый занимающий по 64 бита, различаются числом битов, отведённых для порядка и мантиссы. Чтобы познакомить читателя с форматами чисел с плавающей точкой ЭВМ семейства VAX, воспользуемся форматом F.
Прежде всего необходим знак для мантиссы. Для этого достаточно одного бита. Затем нужен порядок числа. В формате F на ЭВМ VAX могут быть представлены числа в диапазоне 2-128 - 2+127. Это примерно эквивалентно десятичному диапазону 10-38 - 10+38, что вполне достаточно для многих задач науки, техники и других областей. Поскольку порядок, представленный числами в диапазоне -128 - +127, может принимать 256 возможных значений, для его представления необходимо 8 битов.
На ЭВМ семейства VAX для задания знака и порядка используется способ, близкий к описанному в предыдущих разделах способу представления с избытком 50. Но здесь избыток равен 128 в десятичном виде (10000000 в двоичном виде). Следовательно, имеется следующая таблица порядков:
Десятичный порядок | Двоичное представление | Шестнадцатеричное представление |
---|---|---|
+127 |
11111111 |
FF |
+1 |
10000001 |
81 |
0 |
10000000 |
80 |
-1 |
01111111 |
7F |
-128 |
00000000 |
00 |
Числа с плавающей точкой в формате F хранятся в 32-битовом длинном слове. Поскольку для знака нужен один бит, для порядка - 8 битов, то для мантиссы остаётся 23 бита (32 - 9). Рассмотрим, например, представление десятичного числа .75, или 3/4 × 20. Поскольку 3/4 - это 1/2 плюс 1/4, мантисса в двоичном представлении есть .110000...0000, порядок с избытком 128 в двоичном виде есть 10000000, что является истинным нулевым порядком. Таким образом, число .75 может быть представлено в виде двоичного числа с плавающей точкой следующим образом:
Знак |
Порядок |
Мантисса |
---|---|---|
0 |
10000000 |
11000000000000000000000 |
Однако 23-битовая мантисса обеспечивает лишь минимально необходимую для большинства вычислений точность. Поэтому в ЭВМ семействах VAX используют очень удачный способ добавления к мантиссе дополнительного бита, что позволяет получать 24-битовую мантиссу. Вспомним, что в нормализованном представлении числа с плавающей точкой ведущая цифра мантиссы никогда не может быть нулём. В двоичной системе имеются только две цифры - 0 и 1. Следовательно, если исключается 0, то ведущей цифрой должна быть 1. Если ведущая цифра всегда 1, то нет нужды указывать её явно в числе. Поэтому этот бит в записи мантиссы опускается; его называют скрытым битом. В результате число .75 представляется так:
Знак |
Порядок |
Мантисса |
---|---|---|
0 |
10000000 |
10000000000000000000000 |
Единственная проблема, связанная с предположением, что некоторый бит всегда 1, состоит в невозможности представления нуля. Другой способ оценить эту проблему даёт рассмотрение числа .5 × 2-128. Поскольку число положительное, знаковый бит равен 0. В силу того, что истинный порядок есть -128, порядок с избытком 128 будет 00000000. Двоичное представление дроби 1/2 есть .10000000...0000. Однако после того, как опускается скрытый бит, все оставшиеся 23 бита будут нулевыми. В результате представление числа .5 × 2-128 состоит из 32 двоичных нулей, а это то же самое, что и представление нуля.
Чтобы решить эту проблему, ЭВМ семейства VAX интерпретирует любое число с плавающей точкой с нулевым знаковым битом и порядком с избытком, равным 0, как представление нуля (это означает, что число .5 × 2-128 не может быть представлено в виде 32-битового числа с плавающей точкой в формате F). Другими словами, любое число в формате F вида
Знак |
Порядок |
Мантисса |
---|---|---|
0 |
00000000 |
??????????????????????? |
есть представление нуля в формате F.
Как отмечалось в разд. 12.3, это приводит к вопросу об интерпретации чисел с отрицательным знаком и нулевым порядком, т.е. чисел вида
Знак |
Порядок |
Мантисса |
---|---|---|
1 |
00000000 |
??????????????????????? |
На ЭВМ семейства VAX такие числа обрабатываются, как резервные операнды. Любая попытка использования такого числа в операции над числами в формате F будет порождать ошибку, называемую ошибкой по использованию резервного операнда с плавающей точкой. Как отмечалось ранее, это может применяться в языках высокого уровня для обнаружения во время выполнения программы переменных с плавающей точкой, значения которых не определены. Программисты, работающие с языком ассемблера, могут воспользоваться резервными операндами для той же цели.
В качестве последнего примера рассмотрим представление в формате F числа +35/64 × 29:
Все это при упаковке в 32 бита даёт следующее:
Знак |
Порядок |
Мантисса |
---|---|---|
0 |
10001001 |
00011000000000000000000 |
Всё вместе, как 32-битовая двоичная строка или длинное слово, это выглядит так:
0100 0100 1000 1100 0000 0000 0000 0000
или, в шестнадцатеричном представлении, - как ^X448C0000.
Можно было бы предположить, что теперь всё закончено и можно просто поместить каждое число в формате F в длинное слово. Однако и здесь существует ещё одна проблема. ЭВМ семейства VAX разрабатывалось так, чтобы обеспечивалась некоторая совместимость с ЭВМ семейства PDP-11, ЭВМ семейства PDP-11 - 16-битовые машины; и 32-битовые, и 64-битовые числа с плавающей точкой в памяти этих ЭВМ размещаются как последовательности 16-битовых слов, причём первой располагается старшая значащая часть числа. Для ЭВМ семейства VAX это неудобно, так как здесь почти повсеместно принято помещать первой младшую значащую часть числа. Числа с плавающей точкой представляют основное исключение из этого правила. Как следствие, когда число в формате F помещается в длинное слово, приходится менять местами первые 16 битов числа с последними 16 битами. Результат выглядит довольно странно:
Двоичное представление |
Шестнадцатеричное представление |
---|---|
0000 0000 0000 0000 0100 0100 1000 1100 |
^X0000448C |
Обратите внимание, что знаковый бит оказывается в середине длинного слова.
Чтобы избежать этого неудобства, числа с плавающей точкой могут записываться в виде последовательности 16-битовых слов. Приведённое выше число может быть представлено в шестнадцатеричном виде следующим образом:
Содержимое |
Адрес |
---|---|
448С |
начальный адрес |
0000 |
начальный адрес +2 |
В этом формате положительный знак, порядок и мантисса выглядят так:
Бит |
15 |
14 |
13 |
12 |
11 |
10 |
9 |
8 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
|
Знак |
Порядок с избытком 128 |
Старшая часть мантиссы |
Адрес |
|||||||||||||
|
Младшая часть мантиссы |
Адрес + 2 |
|||||||||||||||
Бит |
31 |
30 |
29 |
28 |
27 |
26 |
25 |
24 |
23 |
22 |
21 |
20 |
19 |
18 |
17 |
16 |
|
Внутри каждого 16-битового слова биты мантиссы расположены справа налево по возрастанию значимости.
Числа с плавающей точкой в формате F иногда называют числами с плавающей точкой одинарной точности. Порядок числа, заданный в 8-битовом поле, позволяет представлять числа приблизительно от .29 × 10-38 до 1.7 × 1038. Точность представления числа составляет примерно одну часть от 223, или примерно семь значащих десятичных цифр.
Числа в формате D, которые иногда называют числами с плавающей точкой двойной точности, имеют 64-битовую длину и занимают поэтому квадраслово. Формат D идентичен формату F. за исключением того, что поле мантиссы расширяется от 23 до 55 битов (добавляются 32 бита; скрытый бит не считается). Число в формате D выглядит так:
Бит |
15 |
14 |
13 |
12 |
11 |
10 |
9 |
8 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
|
Знак |
Порядок с избытком 128 |
Самая старшая часть мантиссы |
Адрес |
|||||||||||||
|
Старшая часть мантиссы |
Адрес + 2 |
|||||||||||||||
|
Младшая часть мантиссы |
Адрес + 4 |
|||||||||||||||
|
Самая младшая часть мантиссы |
Адрес + 6 |
|||||||||||||||
Бит |
63 |
62 |
61 |
60 |
59 |
58 |
57 |
56 |
55 |
54 |
53 |
52 |
51 |
50 |
49 |
48 |
|
Дополнительные биты поля мантиссы допускают представление чисел с точностью, примерно равной одной части от 255, или примерно 16 значащих десятичных цифр. Число +35/64 × 29 в формате D будет таким:
Содержимое |
Адрес |
---|---|
448С |
начальный адрес |
0000 |
начальный адрес + 2 |
0000 |
начальный адрес + 4 |
0000 |
начальный адрес + 6 |
Как квадраслово это будет ^X000000000000448C.
Представление чисел с плавающей точкой в форматах типов F и D, используемое в ЭВМ семейства VAX, совпадает с представлением таких чисел на ЭВМ семейства PDP-11, выпускавшихся фирмой DEC. Применение одного и того же представления чисел с плавающей точкой помогло сохранить программную совместимость этих двух семейств ЭВМ. Существует, однако, новый стандарт для представления чисел с плавающей точкой, который был одобрен Институтом инженеров по электротехнике и радиоэлектронике (IEEE).
Стандартное представление IEEE чисел с плавающей точкой базируется на той же философии, что и стандарт фирмы DEC. Однако разбиения по числу битов в полях отличаются. Для согласования с ожидаемыми стандартами на языки высокого уровня, ориентированными на стандарт IEEE, в ЭВМ семейства VAX введены два новых формата чисел с плавающей точкой, отвечающие требованиям стандартов IEEE. Они называются форматами типов G и H. Для представления числа в формате G используется 64 бита, как и в формате D. Однако порядок занимает более длинное поле, а мантисса - соответственно поле меньшей длины. Это означает, что формат G несколько менее точный, но охватывает значительно больший диапазон чисел, чем формат D (см. диапазоны и точность представления в табл. 12.1). Числа в формате G имеют следующий вид:
Бит |
15 |
14 |
13 |
12 |
11 |
10 |
9 |
8 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
|
Знак |
Порядок с избытком 1024 |
Самая старшая |
Адрес |
|||||||||||||
|
Старшая часть мантиссы |
Адрес + 2 |
|||||||||||||||
|
Младшая часть мантиссы |
Адрес + 4 |
|||||||||||||||
|
Самая младшая часть мантиссы |
Адрес + 6 |
|||||||||||||||
Бит |
63 |
62 |
61 |
60 |
59 |
58 |
57 |
56 |
55 |
54 |
53 |
52 |
51 |
50 |
49 |
48 |
|
Поле порядка с избытком 1024 находится в, битах 14 - 4, а самая старшая часть мантиссы занимает битовые позиции 3-0.
Для представления чисел в формате Н используется 128 битов, что в два раза больше, чем для форматов типов D и G. И для порядка, и для мантиссы отводятся дополнительные биты, что обеспечивает расширенный диапазон значений и значительно большую точность. Число в формате Н имеет такой вид:
бит |
15 |
14 |
13 |
12 |
11 |
10 |
9 |
8 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
|
Знак |
Порядок с избытком 16384 |
Адрес |
||||||||||||||
|
Самая старшая часть мантиссы |
Адрес + 2 |
|||||||||||||||
|
Старшая часть мантиссы |
Адрес + 4 |
|||||||||||||||
|
. . . |
Адрес + 6 |
|||||||||||||||
|
. . . |
Адрес + 8 |
|||||||||||||||
|
. . . |
Адрес + 10 |
|||||||||||||||
|
Младшая часть мантиссы |
Адрес + 12 |
|||||||||||||||
|
Самая младшая часть мантиссы |
Адрес + 14 |
Как и форматы типов F и D, форматы типов G и Н имеют однобитовое поле знака числа, используют представление порядка с излишком 2C-1 (где С - число битов порядка) и скрытый бит в мантиссе. Напомним, что пропуск скрытого бита даёт один дополнительный бит точности по сравнению с теми представлениями, где явно указывается каждый бит мантиссы. Особенности четырёх типов форматов с плавающей точкой приведены в табл. 12.1.
Тип |
Длина поля, бит |
Диапазон значений |
Число |
|||
---|---|---|---|---|---|---|
Полная |
Знак |
Порядок |
Мантисса* |
|||
F |
32 |
1 |
8 |
23 |
.29 × 10-38 - 1.7 × 1038 |
7 |
D |
64 |
1 |
8 |
55 |
.29 × 10-38 - 1.7 × 1038 |
16 |
G |
64 |
1 |
11 |
52 |
.56 × 10-308 - .9 × 10308 |
15 |
Н |
128 |
1 |
15 |
112 |
.84 × 10-4932 - .59 × 104932 |
33 |
*Не учитывается наличие скрытого бита. |
|
|
В гл. 6 был дан полный список инструкций целочисленной арифметики. В основном это были операции сложения, вычитания, умножения и деления. Для каждой арифметической операции было описано некоторое семейство инструкций. Каждое семейство включало инструкции с двумя и тремя операндами для работы с 8-битовыми, 16-битовыми и 32-битовыми целыми числами. Поэтому каждое семейство насчитывало по шесть членов. Например, семейство инструкций сложения имеет следующие шесть членов:
ADDB2 |
ADDB3 |
ADDW2 |
ADDW3 |
ADDL2 |
ADDL3 |
Когда рассматриваются операции с плавающей точкой, к каждому семейству инструкций добавляется ещё по восемь членов. Это инструкции с двумя и тремя операндами для выполнения арифметических операций с числами в формате типов F, D, G и H. В результате получается 32 дополнительные инструкции:
ADDF2 |
ADDF3 |
SUBF2 |
SUBF3 |
MULF2 |
MULF3 |
DIVF2 |
DIVF3 |
ADDD2 |
ADDD3 |
SUBD2 |
SUBD3 |
MULD2 |
MULD3 |
DIVD2 |
DIVD3 |
ADDG2 |
ADDG3 |
SUBG2 |
SUBG3 |
MULG2 |
MULG3 |
DIVG2 |
DIVG3 |
ADDH2 |
ADDH3 |
SUBH2 |
SUBH3 |
MULH2 |
MULH3 |
DIVH2 |
DIVH3 |
Эти инструкции работают так, как описано ранее, выполняя указанные операции над парами 32-битовых, 64-битовых или 128-битовых чисел с плавающей точкой. Буквы F, D, G и Н в мнемонике инструкций определяют соответствующие форматы операндов.
Формат инструкций в машинном коде, работающих с операндами типов F и D, совпадает с форматом арифметических инструкций для работы с операндами в формате байта, слова и длинного слова. Единственное отличие заключается в использовании других кодов операций. Однако формат инструкций для операндов типов G и Н несколько отличается. Эти инструкции были введены в архитектуру ЭВМ семейства VAX после того, как большинство 8-битовых кодов операций уже были задействованы для других инструкций. В результате этого большинство кодов операций для операций над числами с плавающей точкой в форматах G и Н требуют двух 8-битовых байтов. Поэтому, хотя, например, код операции для инструкции ADDF2 есть ^X40, а код для инструкции ADDD2 есть ^X60, код операции для инструкции ADDG2 равен ^X40FD, а для инструкции ADDH2 - ^X60FD. Фактически все коды операций для инструкций с операндами типов G и Н содержат в младших восьми битах код ^XFD. Это признак расширения кода. Вспомним, что младшие 8 битов анализируются первыми. Сам по себе код ^XFD не является допустимым кодом операции для какой-либо инструкции, он лишь предписывает процессору извлечь следующий байт, чтобы сформировать расширенный 16-битовый код операции.
Чтобы вместе с инструкциями для работы с числами с плавающей точкой можно было использовать инструкции условных переходов, при выполнении этих инструкций производят установку кодов условий. Если результат отрицательный, устанавливается бит N, если нулевой - бит Z. Бит V устанавливается, если происходит переполнение или потеря значимости.
Как отмечалось ранее, переполнение числа с плавающей точкой происходит при попытке вычислить такое число, значение которого слишком велико, чтобы его можно было представить в заданном формате. Для чисел в форматах F и D это значение приблизительно равно 1.7 × 1038. Многие операционные системы прекращают выполнение программы, если происходит переполнение числа с плавающей точкой. В результате программа может не обнаружить , что установлен бит V при переполнении с плавающей точкой.
Потеря значимости числа с плавающей точкой происходит при попытке вычислить такое число, которое не является нулём, но имеет настолько маленькое значение, что не может быть представлено в данном формате числа с плавающей точкой (для чисел в форматах F и D наименьшее ненулевое значение с плавающей точкой равно приблизительно .29 × 10-33 ). В большинстве случаев потеря значимости даёт нулевой результат. Однако слишком большое число повторений ошибки потери значимости может служить признаком того, что выбранная для данной задачи шкала единиц не обеспечивает необходимую значимость. По этой причине в большинстве операционных систем при обнаружении потери значимости результат операции обнуляется, но при этом также выдаётся предупреждающее сообщение или производится останов по ошибке, если за время выполнения программы случается больше чем несколько (вероятно, 10) ошибок такого рода. Фактическое значение максимально допустимого числа ошибок может быть параметром, который пользователь может модифицировать при запуске программы.
До сих пор обсуждались биты N, Z и V, а что можно сказать о бите С? Поскольку операции над числами с плавающей точкой не порождают переноса в том смысле, как это делают при целочисленных операциях, бит С не используется. Поэтому при выполнении операций над числами с плавающей точкой бит С сбрасывается в 0.
Все перечисленные выше арифметические инструкции вызывают прерывание по ошибке, называемой ошибкой по использованию резервного операнда с плавающей точкой, если делается попытка использовать число с плавающей точкой со знаковым битом, равным 1, и порядком с избытком, равным 0. Фактически почти все инструкции для работы с числами с плавающей точкой ведут себя таким образом (единственное исключение касается специального семейства инструкций POLY, которое будет описано позднее).
В дополнение к основным арифметическим операциям сложения, вычитания, умножения и деления существуют ещё несколько вспомогательных операций. Это операции пересылки, очистки, проверки и сравнения. Они выполняются с помощью следующих инструкций:
MOVF |
CLRF |
TSTF |
CMPF |
MOVD |
CLRD |
TSTD |
CMPD |
MOVG |
CLRG |
TSTG |
CMPG |
MOVH |
CLRH |
TSTH |
CMPH |
Как и ожидалось, эти инструкции выполняют операции пересылки числа с плавающей точкой из одной области памяти в другую, очистки области памяти для числа с плавающей точкой, проверки того, является ли число отрицательным или равно, нулю, и сравнения значений двух чисел с плавающей точкой.
Читателю может показаться, что поскольку число в формате F занимает 4 байта и столько же занимает длинное слово, то можно заключить, что MOVF - это та же самая инструкция, что и MOVL, поскольку они обе пересылают четыре байта из одного места в другое. Однако имеются некоторые тонкие отличия.
С одной стороны, разные правила, которые используются для установки кодов условий. Для инструкции MOVL бит Z устанавливается тогда, и только тогда, когда все 32 бита содержат значение 0. Для числа с плавающей точкой бит Z устанавливается, если равны 0 как бит знака числа, так и порядок с избытком (значение мантиссы в расчёт не принимается), Бит N устанавливается по-разному, поскольку бит знака числа расположен в разных позициях длинного слова. Знаковым битом целочисленного длинного слова является бит 31 (самый левый бит), тогда как знаковый бит числа с плавающей точкой - это всегда бит 15 (старший бит второго байта). Наконец, в инструкциях весьма по-разному обрабатываются резервные операнды. Для инструкции MOVL резервный операнд не имеет никакого особого значения, но попытка пересылки резервного операнда с помощью инструкции MOVF будет порождать ошибку по использованию резервного операнда с плавающей точкой. Как следствие, инструкции MOVF и MOVL имеют разные коды операций и являются разными инструкциями.
С другой стороны, сброс в 0 содержимого целочисленной ячейки требует тех же самых шагов, что и сброс в нуль содержимого ячейки с числом в формате F. Поэтому CLRF - это та же самая инструкция, что и CLRL. То есть для мнемоники CLRF и CLRL порождается один и тот же код операции. Аналогичные замечания относятся и к другим форматам чисел с плавающей точкой. Например, инструкции MOVQ (переслать квадрослово), MOVD и MOVG - разные инструкции, но CLRQ, CLRD и CLRG имеют один и тот же код операции и в действительности это просто разные имена одной и той же инструкции.
Излишние мнемоники языка ассемблера встроены в ассемблер исключительно с целью облегчить жизнь программистов, работающих с языком ассемблера. Во-первых, программист освобождается от необходимости помнить, какие инструкции различны, какие - нет (MOVF отличается от MOVL, но CLRF - это то же самое, что CLRL). Во-вторых, согласованное использование мнемоник языка ассемблера обеспечивает хорошую документируемость программ.
Кроме перечисленных выше инструкций имеется ещё несколько дополнительных инструкций, двойники которых использовались в предыдущих главах для работы с целыми числами. Они снова группируются по четыре инструкции - для каждого формата чисел с плавающей точкой. Первый набор инструкций такой:
MNEGF |
MNEGD |
MNEGG |
MNEGH |
Эти инструкции выполняют пересылку отрицательного значения исходного операнда по адресу назначения, как это делают инструкции пересылки целых чисел с изменением знака. Поскольку изменение знака числа с плавающей точкой состоит просто в изменении на обратное значения знакового бита, эти инструкции отличаются от своих целочисленных дубликатов, которые имеют дело с дополнением до двух.
Другое семейство инструкций предназначено для адресных операций. Это такие инструкции:
MOVAF |
MOVAD |
MOVAG |
MOVAH |
PUSHAF |
PUSHAD |
PUSHAG |
PUSHAH |
Поскольку адрес 4-байтового блока данных не зависит от того, для чего эти данные предназначаются, нет никакого реального различия между адресацией 32-битового числа с плавающей точкой и длинного слова. Поэтому инструкция MOVAF идентична инструкции MOVAL, а инструкция PUSHAF идентична инструкции PUSHAL. Аналогичные замечания относятся и к остальным шести инструкциям.
Инструкции для работы с числами с плавающей точкой могут использовать непосредственные операнды в существенной степени так же, как это делается в инструкциях для работы с целыми числами. Так, например, по инструкции ADDF2 #3.1415927,X выполняется сложение приблизительного значения числа π с числом с плавающей точкой X, имеющим формат F. Синтаксис написания констант с плавающей точкой, хотя и несколько менее строгий, в существенной степени совпадает с тем, который используется в Фортране. Вместо описания здесь точных правил достаточно будет сказать, что любое допустимое представление вещественных констант в Фортране так же допустимо и в языке ассемблера ЭВМ семейства VAX. Сюда входит, конечно, и экспоненциальный формат Е. (Более подробную информацию см. в руководстве "VAX-11 Macro Language Reference Manual".)
В инструкциях для работы с числами с плавающей точкой могут использоваться литеральные операнды. Как и в случае целочисленных инструкций, если первые два бита байта спецификатора операнда нулевые, то остальные шесть битов используются для ограниченных значений непосредственного операнда. Для целых, чисел ограничение состоит в том, что число должно попадать в диапазон 0 - 63. Для операций над числами с плавающей точкой ограничение заключается в том, что число с плавающей точкой должно быть представимо в специальном 6-битовом формате. Очевидно, что такое число, как 3.1415927, в шести битах представлено быть не может. Поэтому в инструкции ADDF2 #3.1415927,X потребуется полный, заданный в формате длинного слова непосредственный операнд. Однако более простые константы с плавающей точкой, такие как 1.0 (которая встречается очень часто) могут быть представлены в 6-битовом формате. Следовательно, инструкция ADDF2 #1.0,X после её ассемблирования будет занимать на четыре байта меньше.
На рис. 12.4 показаны 64 числа с плавающей точкой, которые могут быть представлены как литералы. 6-битовый литерал с плавающей точкой состоит из 3-битового порядка и 3-битовой мантиссы со скрытым битом. Представление отрицательных значений и отрицательных порядков не поддерживается. Поэтому диапазон дробей простирается от 1000 до 1111 (в двоичном виде). (Заметим, что ведущая единица мантиссы опущена так, что для мантиссы требуется только 3 бита памяти.) Поскольку подразумевается, что точка в позиционном представлении числа находится слева, эти дроби позволяют представить значения в диапазоне 1/2 - 15/16 с шагом по 1/16. Значения порядка числа находятся в диапазоне 0-7, так что числа могут быть представлены с ограниченной точностью от 1/2 × 20, или 1/2, до 15/16 × 27, или 120.
Биты |
Биты мантиссы (ведущий бит скрыт) |
|||||||
---|---|---|---|---|---|---|---|---|
000 |
001 |
010 |
011 |
100 |
101 |
110 |
111 |
|
000 |
½ |
9/16 |
⅝ |
11/16 |
¾ |
13/16 |
⅞ |
15 /16 |
001 |
1 |
1 ⅛ |
1 ¼ |
1 ⅜ |
1 ½ |
1 ⅝ |
1 ¾ |
1 ⅞ |
010 |
2 |
2¼ |
2 ½ |
2 ¾ |
3 |
3 ¼ |
3 ½ |
3 ¾ |
011 |
4 |
4 ½ |
5 |
5 ½ |
6 |
6 ½ |
7 |
7 ½ |
100 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
101 |
16 |
18 |
20 |
22 |
24 |
26 |
28 |
30 |
110 |
32 |
36 |
40 |
44 |
48 |
52 |
56 |
60 |
111 |
64 |
72 |
80 |
88 |
96 |
104 |
112 |
120 |
Рис. 12.4. Литералы с плавающей точкой
В дополнение к непосредственным константам в инструкциях иногда полезно иметь константы, размещённые в области данных программы. Это, в частности, полезно для создания массивов констант. В случае целых чисел применяются директивы ассемблера .BYTE, .WORD, .LONG и .QUAD. Для вещественных констант применяются директивы .F_FLOATING, .D_FLOATING, .G_FLOATING и .H_FLOATING[4]. Эти директивы могут иметь переменное число аргументов, синтаксис констант совпадает с принятым в Фортране. Например, по следующей директиве:
.F_FLOATING 1.0,2.0,3.0
будет сформирован массив длинных слов:
^X00004080 ^X00004100 ^X00004140
В гл. 6 рассматривались инструкции пересылки байтов в слова, слов в длинные слова и т.д. Теперь это множество инструкций будет расширено, будет разрешено не только преобразование операндов с плавающей точкой одного типа (таких как одинарной точности - формат F) в операнды другого типа (таких как двойной точности - формат D), но также всех комбинаций преобразований целых чисел в числа с плавающей точкой и наоборот. На рис. 12.5 показано семейство инструкций преобразования.
Как и следовало ожидать, при пересылке вещественного числа в целочисленное поле дробная часть числа теряется. Для инструкций на рис. 12.5 это делается с помощью усечения. Также возможно, что вещественное число может оказаться слишком большим для размещения в поле байта, слова или даже длинного слова при его преобразовании в целое. В таком случае может произойти переполнение. Для индикации переполнения используется бит V, как это делается в случае инструкций, преобразующих большие форматы целых чисел в меньшие, таких как CVTWB. Обратите внимание, что при употреблении инструкции CVTLF может происходить потеря точности, поскольку в длинном слове значение числа занимает 31 бит, но только 24 бита занимает мантисса числа в формате F.
Тип операнда источника |
Тип операнда получателя | ||||||
---|---|---|---|---|---|---|---|
Байт | Слово | Длинное слово | Формат F | Формат D | Формат G | Формат H | |
Байт |
|
CVTBW |
CVTBL |
CVTBF |
CVTBD |
CVTBG |
CVTBH |
Слово | CVTWB |
|
CVTWL |
CVTWF |
CVTWD |
CVTWG |
CVTWH |
Длинное слово | CVTLB |
CVTLW |
|
CVTLF |
CVTLD |
CVTLG |
CVTLH |
Формат F | CVTFB |
CVTFW |
CVTFL |
|
CVTFD |
CVTFG |
CVTFH |
Формат D | CVTDB |
CVTDW |
CVTDL |
CVTDF |
|
|
CVTDH |
Формат G | CVTGB |
CVTGW |
CVTGL |
CVTGF |
|
|
CVTGH |
Формат H | CVTHB |
CVTHW |
CVTHL |
CVTHF |
CVTHD |
CVTHG |
|
Рис. 12.5. Семейство инструкций преобразования
Заметим, что отсутствуют инструкции для преобразования чисел между форматами D и G. Чтобы преобразовать формат D в формат G, необходимо преобразовать 64-битовое число в формате D в 128-битовое число в формате Н, а затем преобразовать число в формате Н в 64-битовое число в формате G. Аналогичные замечания относятся и к преобразованию из формата G в формат D. Преобразования могут выполняться с помощью следующих макроинструкций:
.MACRO CVTDG D_FLT,G_FLT CVTDH D_FLT,-(SP) CVTHG (SP)+,G_FLT .ENDM CVTDG |
.MACRO CVTGD G_FLT,D_FLT CVTGH G_FLT,-(SP) CVTHD (SP)+,D_FLT .ENDM CVTGD |
Как отмечалось ранее, инструкции на рис. 12.5, осуществляющие преобразование формата с плавающей точкой в целочисленный формат, делают это с усечением дробной части. В ЭВМ семейства VAX имеются четыре дополнительные инструкции, которые при преобразовании округляют дробную часть до ближайшего целого числа. Это такие инструкции:
CVTRFL |
CVTRDL |
CVTRGL |
CVTRHL |
Например, инструкция CVTRFL (преобразовать число в формате F в целое в формате длинного слова с округлением) будет округлять число с плавающей точкой до ближайшего целого числа и помещать результат в длинное слово.
Программы выполняющие большой объём вычислений с числовыми дробями, зачастую называют "перемалывателями" чисел. Однако даже такие программы много своего времени (если не большую часть) тратят на выполнение логических и целочисленных операций. Сюда входят адресные вычисления для массивов, подсчёт числа итераций цикла и определение того куда должен быть осуществлён переход. Чем ниже уровень, на котором выполняются эти логические операции, тем быстрее будет работать программа. Поэтому, например, циклы, требуемые для выполнения умножения, деления и операций с плавающей точкой, на быстродействующих ЭВМ, уже давно реализуются аппаратно. Современной тенденцией является вычисление полиномов. Простой полином может быть определён так:
АnХn + An-1Xn-1 + ... +А2Х2 + А1Х1 + А0
где А0, ..., Аn образуют массив постоянных коэффициентов, а X - это переменная. Поскольку n - наивысший показатель степени, это говорит о том, что полином имеет порядок n. Полиномы очень важны для численных вычислений, поскольку большинство функций, таких как SIN, COS и EXP, возвращающих вещественное значение, могут быть аппроксимированы полиномами. Например, функцию ех можно аппроксимировать полиномом
1 + X + X2/2! + X3/3! + Х4/4! + ... + Хn/n!
Вычисляя такой полином достаточно высокого порядка, можно достичь любой желаемой точности.
Для вычисления полиномов предназначаются инструкции POLYF, POLYD, POLYG и POLYH. Эти инструкции имеют три операнда: переменная X в формате числа с плавающей точкой; целочисленное значение порядка полинома; адрес массива, который содержит коэффициенты, имеющие формат числа с плавающей точкой. Начиная с коэффициента члена наивысшего порядка для записи результата вычисления операнда нет. Вместо этого для результата всегда используются регистры общего назначения R0, R1, R2. В этих инструкциях регистры общего назначения также используются для сохранения промежуточных значений во время вычисления. Поэтому после завершения выполнения инструкций содержимое некоторых из этих регистров будет перезаписано. Инструкция POLYF помещает результат в регистр R0, но также перезаписывает регистры R1, R2 и R3. Инструкции POLYD и POLYG помещают старшие 32 бита результата в регистр R0, а младшие 32 бита - в регистр R1. Они также изменяют содержимое регистров от R2 до R5. Инструкция POLYH помещает 128-битовый результат в регистры R0 - R3, причём старшие биты заносятся в регистр R0, а самые младшие - в регистр R3. Эта инструкция также изменяет содержимое регистров R4 и R5.
Заметим, что использование регистров R0 и R1 для запоминания результата двойной точности является обычным способом, применяемым для всех инструкций типа D или G, и, когда один или несколько операндов находятся в регистрах общего назначения, указанный в инструкции регистр будет содержать старшие 32 бита числа, а регистр со следующим номером будет содержать младшие 32 бита. Таким образом, за инструкцией POLYD может следовать инструкция MOVD R0,ANS или даже инструкция ADDD2 R0,TOTAL.
В качестве примера работы этих инструкций рассмотрим приведённый выше полином для аппроксимации функций еX. И хотя точность не будет очень высокой, если только значение X не малое, будем считать, что для аппроксимации вполне достаточно иметь полином пятого порядка. Тогда нам нужна таблица из шести коэффициентов:
CTAB: .F_FLOATING 0.8333333E-2 ;1/5! .F_FLOATING 0.04166667 ;1/4! .F_FLOATING 0.166667 ;1/3! .F_FLOATING 0.5 ;1/2! .F_FLOATING 1.0 ;1/1! .F_FLOATING 1.0 ;1/0!
Затем по инструкции POLYF X,#5,CTAB можно будет вычислить функцию, помещая результат в регистр R0.
Семейство инструкций EMOD (расширенное умножение и отделение целой части) используется для вычисления значения периодических функций, таких как тригонометрические функции (как поясняется ниже, буквы MOD происходят от слова модуль). Рассмотрим синус угла, заданного в градусах. По определению функции синуса имеем
SIN(X) = SIN (X + 360) = SIN(X + 720) = SIN(X + N × 360),
где N - любое целое число. При вычислении синуса угла числа, кратные 360, являются избыточными. Для вычисления значений периодических функций в программах обычно осуществляется приведение к заданному интервалу, которое позволяет уменьшать значение аргумента функции так, чтобы оно попадало в некоторый ограниченный интервал. Например, для функции синуса приведение к заданному интервалу может быть выполнено делением значении аргумента на 360 (а это то же самое, что умножение на 1/360) с последующим отбрасыванием целой части результата, после чего вычисление синуса производится для дробной части результата (в данном примере число 360 является модулем, по которому вычисляется остаток, что и нашло отражение в мнемонике инструкции).
Однако при сокращении интервала может происходить потеря значащих цифр. Например, если операнды являются числами в формате F и выполняется сокращение для SIN(360001), то 360001/360 равно 1000 плюс 1/360. Первоначально точность числа в формате F определяется примерно 23 битами. Отбрасывание целой части - 1000 отнимает примерно 10 битов точности. Это означает, что точность дробной части определяется примерно как 23 - 10 = 13 битов.
Семейство инструкций EMOD позволяет разрешить проблему. Рассмотрим, например, следующую инструкцию этого семейства:
EMODF MULT,MULT_EXT,ARG,INT_PART,FRACT_PART
Первый, третий и пятый операнды (MULT, ARG и FRACT_PART) - это числа в формате F. Второй операнд, MULT_EXT, - это байт, используемый для 8 битов дроби дополнительно к 24 битам дроби в операнде MULT. В вышеприведённом примере программист для числа 1/360 мог задать 32 значащие цифры (двоичные). Старшие 24 бита становятся мантиссой числа MULT в формате F, а младшие 8 бит попадают в байт MULT_EXT.
При выполнении инструкции EMODF аргумент ARG в формате F умножается на расширенный множитель (MULT, расширенный байтом MULT_EXT). Целая часть результата помещается в целое слово INT_PART, а приведённый к заданному интервалу результат помещается по адресу FRACT_PART, как число формата F (после чего можно воспользоваться инструкцией POLYF, использующей FRACT_PART, для вычисления синуса). Инструкции EMODD, EMODG и EMODH работают аналогично. Однако длина операндов (включая MULT_EXT и INT_PART) отличается.
а) |
1.0 |
б) |
-10.0 |
в) |
100.0 |
г) |
0.5 |
д) |
0.1 |
е) |
-0.01 |
а. |
^X00004080 |
б. |
^X00004000 |
в. |
^X00003F80 |
г. |
^X00006080 |
д. |
^X0000C080 |
е. |
^XCCCC3F4C |
0.5 × (X + N/X),
где X - начальное приближение заданного значения квадратного корня из N. Если начальное значение X оказывается достаточно близким к корню квадратному из N, то формула даст значение, намного более близкое к правильному. В результате этого, если алгоритм обеспечивает удачное определение первоначального значения, несколько итераций этой формулы позволяют получить результат с точностью до 15 - 30 знаков (десятичных). Один простой алгоритм для задания начального приближенного значения работает следующим образом:
f × 2C, где 1/2 ≤ f < 1;
√f' × 2C', где 1/2 ≤ f < 2;
√f' × 2C/2;
0.707 ≤ √f' ≤ 1.41;
Напишите подпрограмму, которую можно вызывать из программы на Фортране и которая в качестве входного аргумента имеет одну переменную в формате D, а в качестве выходного - массив из десяти переменных в формате D. Эта подпрограмма будет применять описанный выше алгоритм для получения первоначального значения корня квадратного из входной переменной. Затем она выполняет девять итераций по формуле 0.5 × (X + N/X) и помещает каждое полученное значение в выходной массив. Напишите программу на Фортране, которая вызывает подпрограмму с различными входными значениями. Программа на Фортране должна для сравнения распечатывать эти результаты вместе с результатами, полученными с помощью функции DSQRT из библиотеки Фортрана. (Подсказка: используйте маскирование и операции сдвига для деления показателя степени на два при определении первоначального значения.)
COS (x) = 1 - X2/2! + Х4/4! - Х6/6! + Х8/8! - ...
Этот ряд может использоваться в ЭВМ для вычисления функции косинуса, если берётся достаточное число членов ряда, чтобы гарантировать необходимую точность. Т.к. от члена к члену ряда знак меняется, то как только члены начнут уменьшаться по абсолютному значению, ошибка будет меньше первого отбрасываемого члена:
Коэффициенты не должны храниться в массиве, а должны вычисляться в процессе вычисления полинома. В этом упражнении нельзя пользоваться инструкцией POLYD или POLYG.
< НАЗАД | ОГЛАВЛЕНИЕ | ВПЕРЁД > |
[1] В некоторых вычислениях процента налога и прибыли имеют дело с долями цента, такой, как, например 1/1000 долл. Для этих случаев описанная здесь схема представления чисел с фиксированной точкой должна быть модифицирована.
[2] Эти цифры теряются, если только операция не выполняется с двойной точностью. Иначе можно было бы округлить число, если самая левая из этих цифр 5 или больше. Такое усечение или округление приводит к неизбежным погрешностям в вычислениях.
[3] Эта цифра теряется. Она может быть сохранена при выполнении операции с двойной точностью или использована для округления результата до +127185. Это аналогично тому, что случилось на шаге 5, и также может отразиться на погрешности вычислений.
[4] Директива .FLOAT даёт тот же эффект, что и директива .F_FLOATING, а директива .DOUBLE - тот же эффект, что директива .D_FLOATING.