ГЛАВА 13. ИНСТРУКЦИИ ОБРАБОТКИ СИМВОЛЬНЫХ, ДЕСЯТИЧНЫХ И ДРУГИХ ДАННЫХ

13.1. ВВЕДЕНИЕ

В этой главе описаны некоторые из более сложных инструкций, которые являются составной частью архитектуры ЭВМ семейства VAX. В архитектуру входят, например, инструкции обработки символьных строк. С помощью этих инструкций можно выполнять такие операции, как пересылка символьной строки, сравнение двух символьных строк или поиск в символьной строке конкретной подстроки. Многие из этих методов были описаны в гл. 8, однако инструкции, описываемые в настоящей главе, позволяют выполнять подобные функции с помощью одной инструкции.

Другое множество инструкций имеет дело с десятичными числами и десятичной арифметикой. Для представления десятичных чисел в архитектуру ЭВМ семейства VAX включено представление чисел, называемое упакованным десятичным представлением. Число в таком представлении хранится в виде последовательности байтов, в которой каждый байт представляет две десятичные цифры. Имеются инструкции для выполнения арифметических операций над упакованными десятичными числами, которые особенно важны для обработки экономической информации.

Ещё одно множество инструкций поддерживает форматирование числовой информации для ввода и вывода. Числовая строка - это символьная строка, которая содержит числовую информацию в одном из нескольких форматов, обеспечиваемых архитектурой ЭВМ семейства VAX. Имеются инструкции для преобразования упакованных десятичных чисел в числовые строки и обратно. Гибкий способ преобразования десятичных чисел в символьные строки обеспечивает инструкция EDIT. Наконец, архитектура ЭВМ VAX включает инструкции обработки битовых полей и двунаправленных списков.

Многие описанные в этой главе инструкции были включены в архитектуру ЭВМ семейства VAX для поддержки языков высокого уровня. Однако большинство этих инструкций выполняют такие функции, которые вообще полезны в любой среде программирования. Их использование в программах на языке ассемблера может обеспечить сохранение значительного количества процессорного времени и пространства памяти.

13.2. ИНСТРУКЦИИ СЕМЕЙСТВА MOVC

ИНСТРУКЦИЯ MOVC3

Как было показано в гл. 8, символьная строка - это последовательность байтов памяти, которая определяется двумя атрибутами: адресом первого байта строки и длиной строки. На рис. 13.1 приведён фрагмент программы для копирования исходной символьной строки в целевую символьную строку. Исходная строка начинается с адреса SOURCE, целевая строка начинается с адреса DEST, а длину строки определяет символическое имя LENGTH Поскольку для загрузки длины строки в регистр R0 используется инструкция MOVZWL, длина - это целое число без знака в формате слова, а исходная и целевая строки могут иметь длину от 0 до 65535 байтов. После завершения цикла регистр R0 будет содержать 0, в регистре R1 будет находиться адрес байта, следующего за последним байтом исходной строки, а в регистре R3 - адрес байта, следующего за последним байтом целевой строки.

В ЭВМ семейства VAX имеется инструкция MOVC3, которая в существенной степени даёт тот же эффект, что и фрагмент программы на рис. 13.1. Например, инструкция

MOVC3   #LENGTH,SOURCE,DEST

скопирует #LENGTH байтов, начиная с адреса SOURCE, по адресу DEST. Кроме того, инструкция MOVC3 использует регистры R0, R1 и R3 так же, как и фрагмент программы на рис. 13.1. После выполнения инструкции MOVC3 регистры R0 - R5 будут содержать следующее :

Обратите внимание, что инструкция MOVC3 модифицирует содержимое регистров R0 - R5, даже если эти регистры не указаны в качестве операндов инструкции. Большинство описываемых в этой главе инструкций неявно используют некоторые или все регистры от R0 до R5 (инструкции семейства POLY, описанные в гл. 12, также неявно используют регистры R0 - R5). Поскольку эти инструкции уничтожают предыдущее содержимое регистров R0 - R5, программист при работе с такими регистрами должен проявлять особое внимание.

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

MOVC3

len,

srcadr,

dstadr

 

 

R0=0

R1=a

R3=a

(R2=R4=R5=0)

a = адрес байта, следующего за последним байтом строки

Имя len обозначает операнд, задающий длину строки, имена srcadr и dstadr представляют адреса исходной и целевой строк. Символы R0=0 под именем len показывают, что значение длины строки помещается в регистр R0 и что после завершения инструкции этот регистр содержит нуль. Символы (R2=R4=R5=0) показывают, что инструкция очищает эти регистры, даже если они прямо и не ассоциируются с каким-либо операндом.

LENGTH=ЛЮБОЕ ЧИСЛО ОТ 0 ДО 65535
SOURCE: .BLKB   LENGTH
DEST:   .BLKB   LENGTH
        ...
        MOVAB   SOURCE,R1       ; УКАЗАТЕЛЬ HA СТРОКУ SOURCE
        MOVAB   DEST,R3         ; УКАЗАТЕЛЬ НА СТРОКУ DEST
        MOVZWL  #LENGTH,R0      ; ДЛИНА - ЦЕЛОЕ БЕЗ ЗНАКА
        BEQL    20$             ; ДЛЯ СТРОКИ С НУЛЕВОЙ ДЛИНОЙ
10$:    MOVB    (R1)+,(R3)+
        SOBGTR  R0,10$
20$:    . . .

Рис. 13.1. Копирование символьной строки

ИНСТРУКЦИЯ MOVC5

Имеется инструкция под названием MOVC5, подобная инструкции MOVC3, которая предназначена для пересылки исходной строки в целевую строку в случае, когда строки имеют разную длину. Формат этой инструкции такой:

MOVC5

srclen,

srcadr,

fill,

dstlen,

dstadr

 

 

R0=a

R1

 

R2=0

R3

(R4=R5=0)

а - число байтов оставшихся непересланными, если длина строки-источника
(srclen) меньше длины строки-получателя (dstlen);
в противном случае 0
б - адрес байта, следующего за последним байтом строки

Первый и четвёртый операнды (srclen и dstlen) - это 16-битовые слова, содержащие длину исходной и целевой строк. Если srclen больше, чем dstlen, то из исходной строки в целевую пересылаются только первые dstlen байтов. Если srclen меньше, чем dstlen, то из исходной строки в целевую пересылается srclen байтов. Оставшиеся dstlen-srclen байтов целевой строки заполняются символом-заполнителем, который определён третьим операндом.

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

SOURCE: .ASCII  /КОРОТКАЯ СТРОКА/
SOURCE_END:
SRC_LEN=SOURCE_END-SOURCE
DST_LEN=25
DEST:   .BLKB   DST_LEN

Тогда выполнение инструкции

MOVC5   #SRC_LEN,SOURCE,#^A"*",#DST_LEN,DEST

установит в целевой строке следующее:

КОРОТКАЯ СТРОКА**********

Третий операнд, #^A"*", иллюстрирует, как в качестве операнда можно использовать строку в коде ASCII. Это операнд эквивалентен операнду #42 или #^X2A, поскольку 42 или ^X2A - это код ASCII символа *. Так как исходная строка имеет длину 14 байтов, а целевая строка - 25 байтов, последние 11 байтов целевой строки будут содержать символы-заполнители, в данном случае *.

В зависимости от соотношения длины исходной и целевой строк инструкция MOVC5 устанавливает коды условий. В частности,

N устанавливается, если длина исходной строки меньше, чем длина целевой строки, как число со знаком;
Z устанавливается при равной длине строк;
V сбрасывается;
С устанавливается, если длина исходной строки меньше, чем длина целевой строки, как число без знака.

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

CMPW    srclen,dstlen

вслед за инструкцией MOVC5. Поэтому после инструкции MOVC5 можно воспользоваться инструкцией условного перехода по сравнению длин двух строк как чисел без знака. Для обеспечения согласованности с инструкцией MOVC5 инструкция MOVC3 устанавливает коды условий так, чтобы отразить сравнение двух строк равной длины. Таким образом, инструкция MOVC3 сбрасывает биты N, V и С и устанавливает бит Z.

ИСПОЛЬЗОВАНИЕ ИНСТРУКЦИЙ СЕМЕЙСТВА MOVC

Инструкция MOVC5 даёт предпочтительный способ заполнения блока памяти символами-заполнителями. Это можно сделать, если задать нулевую длину исходной строки. Например, положим, что следующая область памяти должна быть инициализирована символами пробела (^X20):

BUFLEN = любое число от 0 до 65535
BUFFER: .BLKB   BUFLEN

Пробелы могут быть пересланы в буфер с помощью инструкции

MOVC5   #0,любой_адрес,#^X20,#BUFLEN,BUFFER

Второй операнд, "любой_адрес", является адресом начала исходной символьной строки. Поскольку длина исходной строки нулевая, этот адрес никогда не используется, и, следовательно, допустим любой адрес, включая нуль. Одно решение состоит в применении символа значения счётчика адреса (.) в качестве второго операнда. Но лучшее решение заключается в том, чтобы в качестве второго операнда использовать указатель стека (SP), так как при этом требуется только один байт.

Инструкции MOVC3 и MOVC5 содержат операнды трёх типов: 32-битовые адреса, 16-битовые длины строк как числа без знака и в случае инструкции MOVC5 - 8-битовый символ-заполнитель. Для операндов длины и символа-заполнителя могут использоваться любые режимы адресации, описанные в гл. 7. Но адресные операнды требуют таких режимов адресации, которые образуют 32-битовый адрес. Таким образом, регистровая, литеральная и непосредственная адресации не могут быть использованы с адресными операндами.

В обеих инструкциях, MOVC3 и MOVC5, исходная и целевая строки могут частично накладываться друг на друга. Рассмотрим, например, следующее:

STRING: .ASCII  /ABCDEF /
        . . .
        MOVC3   #5,STRING,STRING+1

Если бы инструкции семейства MOVC выполняли пересылку по одному байту, как показано на рис. 13.1, то байт STRING был бы переслан в байт STRING+1, новое значение байта STRING+1 было переслано в байт STRING+2, и т.д. В результате символ, размещённый по адресу STRING, был бы распространён на остальные пять байтов и после выполнения строка примет вид "AAAAAA"[1].

Однако реализация инструкций семейства MOVC на ЭВМ семейства VAX такова, что исходная строка как бы сначала копируется во временную память, а затем пересылается в целевую строку. В результате после выполнения инструкции MOVC3 строка будет иметь вид "AABCDE".

13.3. ИНСТРУКЦИИ ОБРАБОТКИ СИМВОЛЬНЫХ СТРОК

Инструкции обработки символьных строк, такие как MOVC3 и MOVC5, могут эффективно реализовать операции над символьными строками в языках высокого уровня. Однако слово "символ" в названии инструкции вводит в заблуждение, т.к. применение этих инструкций не ограничивается каким-либо конкретным символьным кодом или даже символами любого рода. Это инструкции общего назначения для обработки байтовых массивов.

Предположим, например, что программа содержит два следующих массива длинных слов:

ELEMENTS=любое число от 0 до 16383
ARRAY_ONE: .BLKL ELEMENTS
ARRAY_TWO: .BLKL ELEMENTS

Инструкция

MOVC5   #0,(SP),#0,#4*ELEMENTS,ARRAY_ONE

может быть использована для очистки всех длинных слов массива ARRAY_ONE (поскольку первый операнд указывает, что длина первой символьной строки нулевая, адрес первой строки не имеет значения; в результате для второго операнда применяется регистровая косвенная адресация с использованием указателя стека (SP)).

Длинные слова массива ARRAY_ONE могут быть скопированы в массив ARRAY_TWO по инструкции

MOVC3   #<4*ELEMENTS>,ARRAY_ONE,ARRAY_TWO

(Инструкция MOVC3 рекомендуется для пересылки блока данных из одного места в памяти в другое.) Аналогично инструкция

MOVC3   #<<4*ELEMENTS>-4>,ARRAY_ONE+4,ARRAY_ONE

устанавливает элементы массива ARRAY_ONE[I] равными элементам ARRAY_ONE [I+1] при изменении I от 1 до ELEMENTS-1. (Операнд #<<4*ELEMENTS>-4> эквивалентен операнду #4*ELEMENTS-4. Угловые скобки, которые обычно применяются для изменения порядка вычисления выражения, включены здесь для улучшения читабельности.)

Все инструкции обработки символьных строк используют некоторые регистры от R0 до R5. Как будет показано, инструкции обработки символьных строк могут иметь до трёх строк в качестве операндов, что требует указания адресов этих строк и их длины. Когда начинается выполнение инструкции обработки символьных строк, процессор производит выборку операндов и загружает длину строк в регистры от R0 до R5 (поскольку выборка операндов осуществляется до использования каких-либо регистров общего назначения, сами операнды могут находиться в этих регистрах, включая регистры R0 - R5). В регистры R0 - R5 загружается следующая информация:

В зависимости от числа строк инструкции обработки символьных строк могут использовать регистры R0 и R1, регистры R0 - R3 или регистры R0 - R5.

В начале выполнения инструкции происходит загрузка значений в регистры R0 и R1, R0 - R3 или R0 - R5, что стирает их предыдущее содержимое. Во время выполнения такой инструкции процессор увеличивает адреса в регистрах с нечётными номерами и уменьшает длину строк в регистрах с чётными номерами в соответствии с тем, как это предусмотрено для конкретной ситуации. После завершения выполнения инструкции содержимое регистров может использоваться для дальнейшей обработки.

Есть ещё одна причина, по которой инструкции обработки символьных строк задействуют регистры общего назначения. Как поясняется в гл. 14, выполнение программы может быть прервано, чтобы процессор смог ответить на событие, имеющее более высокий приоритет, чем текущая программа. Большинство инструкций ЭВМ семейства VAX имеют короткое время выполнения, и процессор обычно, прежде чем начать обработку прерывания, ожидает завершения выполнения текущей инструкции. Однако некоторые инструкции, включая инструкции обработки символьных строк, могут иметь продолжительное время выполнения (даже на такой вычислительной системе, как VAX, пересылка 65 535 байтов может занять значительную долю секунды). Чтобы дать возможность ЭВМ семейства VAX быстро отвечать на внешние события, инструкции с продолжительным временем выполнения разработаны так, что они могут быть прерваны, не дожидаясь их завершения. Поскольку инструкции обработки символьных строк в качестве временной памяти используют регистры R0 - R5, обработка прерываний происходит быстро и эффективно. Когда такие инструкции прерываются в середине выполнения, большая часть информации, необходимой для возобновления выполнения инструкции, находится в регистрах общего назначения.

13.4. ИНСТРУКЦИИ СЕМЕЙСТВА CMPC

Семейство инструкций CMPC (сравнить символьные строки) состоит из двух инструкций, CMPC3 и CMPC5. Подобно всем инструкциям сравнения, инструкции семейства CMPC просто устанавливают коды условий. Инструкции CMPC3 и CMPC5 имеют тот же формат, что и инструкции MOVC3 и MOVC5. Например, инструкция

CMPC3   #10,ALPHA,BETA

выполняет последовательно десять сравнений байтов:

CMPB    ALPHA,BETA
CMPB    ALPHA+1,BETA+1
CMPB    ALPHA+2,BETA+2
. . .
CMPB    ALPHA+9,BETA+9

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

Инструкция CMPC3 модифицирует содержимое регистров от R0 до R3. Когда строки равны, регистр R1 содержит адрес байта, следующего за последним байтом первой строки, регистр R3 содержит адрес байта, следующего за последним байтом второй строки, а содержимое регистров R0 и R2 сбрасывается. Однако, когда строки не равны, регистры R1 и R3 содержат адреса не совпавших при сравнении байтов, а регистр R0 содержит число байтов, оставшихся в каждой строке, включая байт, на котором это произошло. Рассмотрим, например, следующее:

GAMMA:  .ASCII  /ABCDEF/
DELTA:  .ASCII  /АВСDХУ/
        . . .
        CMPC3   #6,GAMMA,DELTA

После выполнения инструкции регистр R0 содержит число 2, а это показывает, что в каждой строке оставалось ещё 2 байта, когда было обнаружено различие. Регистр R1 содержит адрес байта, в котором находится символ Е, регистр R3 содержит адрес байта с символом X, а регистр R0 содержит нуль.

Инструкция CMPC5 предназначена для сравнения двух строк, которые могут иметь разную длину. Она имеет такой же формат, как инструкция MOVC5. Инструкция CMPC3 работает так, как если бы более короткая из двух сравниваемых строк была бы дополнена символами-заполнителями до достижения длины более длинной строки. Рассмотрим для примера следующие строки:

ONE:    .ASCII  /ABC/
TWO:    .ASCII  /ABC**!/

Строки, сравниваемые следующими инструкциями, изображены справа от каждой инструкции:

Инструкция Сравнение строк Причина N Z V C

CMPC5

#3,ONE,#^A"$",#6,TWO

ABC$$$

<

ABC**!

$

<

*

1

0

0

1

CMPC5

#3,ONE,#^A"*",#6,TWO

ABC***

>

ABC**!

*

>

!

0

0

0

0

CMPC5

#6,TWO,#^A"$",#3,ONE

ABC**!

>

ABC$$$

*

>

$

0

0

0

0

CMPC5

#6,TWO,#^A"*",#3,ONE

ABC**!

<

ABC***

!

<

*

1

0

0

1

Если символьные строки равны, бит Z устанавливается в 1, а биты N и С сбрасываются в нуль. Если строки не равны, биты N и С устанавливаются в зависимости от байтов, вызвавших несовпадение. Бит N устанавливается, если значение байта первой строки меньше значения байта второй строки, как число со знаком, тогда как бит С устанавливается, если значение первого байта меньше значения второго байта как число без знака.

В начале выполнения инструкции CMPC5 длины двух строк помещаются в регистры R0 и R2, а адреса строк - в регистры R1 и R3. Если строки равны, то при завершении инструкции регистры R0 и R2 будут содержать 0, а регистры R1 и R3 будут содержать адреса байтов. непосредственно следующих за последним байтом соответствующей строки.

Если строки не равны, регистры R0 и R2 показывают число байтов, оставшихся в соответствующих строках, включая байт, на котором произошло несовпадение (если не совпавший байт является одним из тех, в которые были добавлены символы-заполнители, то соответствующий регистр будет содержать нуль). Регистры R1 и R3 содержат адреса байтов, вызвавших несовпадение (если не совпавшим байтом является один из тех, в которые были добавлены символы-заполнители, то соответствующий регистр будет содержать адрес байта, следующий за последним байтом строки).

Символ-заполнитель используется в инструкции MOVC5, только если первая строка короче второй. В инструкции CMPC5 символ-заполнитель может использоваться независимо от того, какая из строк длиннее. В результате этого выполнение последовательности инструкции MOVC5 и CMPC5 с идентичными операндами не обязательно приводит к совпадению результатов. Например:

THREE:  .ASCII  "ABCDEF"
FOUR:   .BLKB   3
        . . .
        MOVC5   #6,THREE,#^A"*",#3,FOUR
        CMPC5   #6,THREE,#^A"*",#3,FOUR

Инструкция MOVC5 перешлёт ABC в строку FOUR. Однако инструкция CMPC5 будет сравнивать строку ABCDEF со строкой ABC***, что даст несовпадение строк.

Как и в семействе инструкций MOVC, в инструкциях CMPC3 и CMPC5 строки могут частично перекрываться. Например, инструкция

CMPC3   #10,STRING,STRING+1

обнаружит совпадение, если только байты, начиная со STRING+1 по STRING+9, равны байту с адресом STRING (если строка содержит AAAAABCDEF, регистр R1 будет содержать адрес пятой буквы А, а регистр R3 будет содержать адрес буквы В после завершения выполнения). Аналогично инструкция

CMPC3  #SIZE,BYTE_ARRAY,#128,#0,(SP)

будет осуществлять сканирование байтового массива BYTE_ARRAY, оставляя по завершении в регистре R1 адрес первого байта, содержимое которого не равно десятичному числу 128 (поскольку вторая строка имеет нулевую длину, операнд (SP) используется как адрес второй строки).

13.5. ДРУГИЕ ИНСТРУКЦИИ ОБРАБОТКИ СИМВОЛЬНЫХ СТРОК

ИНСТРУКЦИИ SKPC, LOCC И MATCHC

Как отмечалось в предыдущем разделе, инструкция CMPC5 может использоваться для пропуска в символьной строке символов, равных символу-заполнителю. Например, рассмотрим следующее:

STRING: .ASCII  "     НАЧИНАЕТСЯ С ПЯТИ ПРОБЕЛОВ"
STRING_END:
LENGTH=STRING_END-STRING
        . . .
        CMPC5   #LENGTH,STRING,#^A" ",#0,(SP)

Поскольку символом-заполнителем является пробел, а вторая строка имеет нулевую длину, инструкция CMPC5 впервые обнаружит неравные байты при сравнении символа-заполнителя с буквой Н в слове НАЧИНАЕТСЯ. После завершения инструкции регистр R1 будет содержать адрес буквы Н.

Такой же эффект можно получить с помощью инструкции SKPC (пропустить символ), включённой в архитектуру ЭВМ семейства VAX. Например, инструкция

SKPC    #^A" ",#LENGTH,STRING

также приведёт к тому, что регистр R1 будет содержать адрес буквы Н. Регистр R0 будет содержать 26 - число байтов, оставшихся в строке (включая букву "Н") после нахождения символа, отличного от пробела. В противоположность инструкции CMPC5 инструкция SKPC всегда сбрасывает в нуль биты N, V и С. Бит Z устанавливается в соответствии со значением в регистре R0. Если в строке обнаруживается символ, отличный от символа-заполнителя, регистр R0 будет содержать число оставшихся в строке байтов, а бит Z сбрасывается. Если все символы строки равны символу-заполнителю, то регистр R0 содержит 0, а бит Z устанавливается. Регистры R0 и R1 - единственные регистры, используемые инструкцией SKPC как временная память.

Инструкция LOCC (обнаружить символ) идентична инструкции SKPC, за исключением того, что она завершается по совпадению байтов вместо их несовпадения. Например, инструкция

STRING: .ASCII  "НАЧИНАЕТСЯ БЕЗ ПРОБЕЛОВ"
STRING_END:
LENGTH=STRING_END-STRING
        . . .
        LOCC    #^A" ",#LENGTH,STRING

по завершению оставит в регистре R1 адрес пробела, следующего за буквой Я в слове НАЧИНАЕТСЯ. Как и инструкция SKPC, инструкция LOCC устанавливает бит Z в зависимости от значения в регистре R0. В приведённом выше примере регистр R0 будет содержать 13 после завершения инструкции, поскольку в строке остаётся 13 байтов (включая символ пробела). Таким образом, бит Z будет сброшен.

Обнаружение какого-либо заданного символа в символьной строке - весьма частая операция. Например, инструкция

LOCC    #^A":",#LENGTH,STRING

может быть использована для поиска символа которым оканчиваются метки в программе на языке ассемблера. Та же самая инструкция может использоваться и для поиска двоеточия в спецификации файла в ОС VAX/VMS. Фактически подпрограмма, которая осуществляет синтаксический разбор спецификации файла, может воспользоваться несколькими инструкциями LOCC для обнаружения таких символов, как ':', '[', ']', и ';' . Аналогично подпрограмма, выполняющая синтаксический разбор выражений в языках высокого уровня, может использовать инструкции LOCC для обнаружения таких символов, как '=', ':', '(', ')', '+', '-', '*' и '/' .

Инструкции SKPC и LOCC могут применяться для обработки строк, состоящих из слов, разделённых одним или несколькими символами-разделителями. В следующем примере в качестве символа разделителя используется звёздочка * (конечно, в обычном тексте разделителем может служить символ пробела) :

STRING: .ASCII  "**СЛОВА*РАЗДЕЛЯЮТСЯ***СИМВОЛОМ*****ЗВЁЗДОЧКА**"
STRING_END:
LENGTH=STRING_END-STRING
        . . .
        SKPC    #^A"*",#LENGTH,STRING ; R1 УКАЗЫВАЕТ НА "С" В "СЛОВА"
        LOCC    #^A"*",R0,R1          ; R1 УКАЗЫВАЕТ НА "*" ПОСЛЕ "СЛОВА"
        SKPC    #^A"*",R0,R1          ; R1 УКАЗЫВАЕТ НА "Р" В "РАЗДЕЛЯЮТСЯ"
        LOCC    #^A"*",R0,R1          ; R1 УКАЗЫВАЕТ НА "*" ПОСЛЕ "РАЗДЕЛЯЮТСЯ"
        SKPC    #^A"*",R0,R1          ; R1 УКАЗЫВАЕТ НА "С" В "СИМВОЛОМ"

Комментарии в каждой строчке программы сообщают о том, адрес какого байта содержит регистр R1 после выполнения инструкции. Обратите внимание, как результаты, оставляемые в регистрах R0 и R1 одной инструкцией, используются как входная информация для следующей инструкции. Слова строки могут быть обработаны с помощью следующего цикла:

        MOVZWL  LENGTH,R0       ;R0 - ДЛИНА СТРОКИ
        MOVAB   STRING,R1       ;R1 - АДРЕС СТРОКИ
10$:    SKPC    #^A"*",R0,R1    ;ПРОПУСТИТЬ "*" НАЧАТЬ СО СЛЕДУЮЩЕГО СЛОВА
        BEQL    20$             ;ЕСЛИ В R0 НУЛЬ, TO КОНЕЦ СТРОКИ
        ОБРАБОТКА, ВЫПОЛНЯЕМАЯ ПРИ ОБНАРУЖЕНИИ НАЧАЛА СЛОВА
        LOCC    #^A"*",R0,R1    ; НАЙТИ "*" В КОНЦЕ СЛОВА
        BEQL    20$             ; ЕСЛИ В R0 НУЛЬ, TO КОНЕЦ СТРОКИ
        ОБРАБОТКА, ВЫПОЛНЯЕМАЯ ПРИ ДОСТИЖЕНИИ КОНЦА СЛОВА
        BRB     10$
20$:    . . .

Инструкция MATCHC (найти соответствие символьной подстроки) близка инструкции LOCC. Однако LOCC обнаруживает первое появление в строке конкретного символа, а инструкция MATCHC обнаруживает первое появление в строке конкретной подстроки. Рассмотрим, например, следующее:

STRING:     .ASCII  "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
LENGTH=26
SUB_STRING: .ASCII  "DEF"
SUB_LENGTH=3
            . . .
            MATCHC  #LENGTH,STRING,#SUB_LENGTH,SUB_STRING

Заметим, что два первых операнда задают длину и начальный адрес строки, а третий и четвёртый операнды определяют длину и начальный адрес подстроки. В этом примере внутри строки будет найдена подстрока DEF. Инструкция использует для временного запоминания данных регистры R0 - R3. В данном случае после выполнения инструкции регистры R0 - R3 будут содержать следующее:

Коды условий N, V и С сбрасываются, а бит Z устанавливается в соответствии со значением в регистре R0. В данном случае регистр R0 содержит число 23, поэтому бит Z будет сброшен.

Если подстрока не найдена, регистр R0 будет содержать нуль, а регистр R1 будет содержать адрес байта, следующего за последним байтом строки. Кроме того, регистр R2 будет содержать длину подстроки (в данном случае 3), а регистр R3 будет содержать адрес начала подстроки. Поскольку содержимое регистра R0 равно 0, бит Z будет установлен.

ИНСТРУКЦИИ ПРЕОБРАЗОВАНИЯ КОДА СИМВОЛОВ

Четыре дополнительные инструкции напоминают инструкции MOVC, LOCC и SKPC, за исключением того, что они используют добавочную таблицу или строку из 256 байтов. Инструкции MOVC, LOCC и SKPC последовательно производят выборку байтов из исходной строки (инструкция MOVC эти байты пересылает в целевую строку; инструкции LOCC и SKPC сравнивают их с символом, определяемым третьим операндом). Инструкции, описываемые в этом разделе, работают аналогично, за исключением того, что после выборки каждого байта из исходной строки он используется как индекс в таблице из 256 байтов. Соответствующий байт извлекается из таблицы и употребляется вместо байта из исходной строки.

Ниже показаны операнды инструкции MOVTC (переслать преобразованные символы). Операнды инструкции MOVC5 также показаны для того, чтобы показать сходство этих двух символов инструкций.

MOVC5   srclen,srcadr,fill,dstlen,dstadr
MOVTC   srclen,srcadr,fill,tbladr,dstlen,dstadr

Обратите внимание, что единственное различие между инструкциями MOVC5 и MOVTC состоит в добавлении операнда tbladr, который задаёт адрес начала 256-байтовой таблицы.

Инструкция MOVTC может использоваться для выполнения преобразований символов из кода ASCII в код EBCDIC или обратно. Она может применяться для преобразования строчных букв в прописные или для преобразования пробелов в нули или наоборот. Но ею можно пользоваться и для других целей. Рассмотрим следующий простой пример, который иллюстрирует работу инструкции MOVTC:

CNT=1
TABLE:
        .REPT   255
        .BYTE   CNT
CNT=CNT+1
        .ENDR
        .BYTE   0
        . . .
ARRAY:  .BYTE   27,255,0,100,1
        . . .
        MOVTC   #5,ARRAY,#0,TABLE,#5,ARRAY

256-байтовая таблица содержит числа 1, 2, 3, ..., 254, 255, 0. Таким образом, значение в BYTE[I] равно I+1 (последний, 256-й байт рассматривается как специальный признак и поэтому содержит 0). Инструкция MOVTC извлекает каждый байт из байтового массива ARRAY и использует его значение как индекс байта в таблице (который содержит значение, на 1 большее номера байта в массиве), выбранный из таблицы байт снова помещается в массив. В результате инструкция MOVTC как бы прибавляет 1 к каждому байту массива, так что после завершения инструкции массив содержит числа 28, 0, 1, 101, 2.

В отношении использования регистров MOVTC рассматривает таблицу как третью символьную строку. Таким образом, регистры R0 и R1 используются для хранения длины и адреса исходной строки, регистры R2 и R3 - для длины (256) и адреса таблицы, а регистры R4 и R5 - для длины и адреса целевой строки. После завершения инструкции MOVTC регистры содержат следующее:

MOVTC

srclen,

srcadr,

fill,

tbladr,

dstlen,

dstadr

 

 

R0

R1

 

R3=адр

R4=0

R5

(R2=0)

a - srclen-dstlen, если srclen > dstlen; в противном случае 0
б - адрес байта, следующего за последним перекодированным байтом
в - адрес байта, следующего за последним байтом строки-получателя

Как и в случае инструкции MOVC5, инструкция MOVTC устанавливает коды условий так, как если бы выполнена пара инструкций:

MOVTC   srclen,srcadr,fill,tbladr,dstlen,dstadr
CMPW    srclen,dstlen

за исключением того, что бит Z сбрасывается.

Инструкция MOVTUC (пересылать преобразованные символы до переключения) работает так же, как инструкция MOVTC, за исключением того, что выполнение завершается, когда из таблицы извлекается определённый символ, называемый символом переключения кода (esc). Описание этой инструкции таково:

MOVTUC

srclen,

srcadr,

esc,

tbladr,

dstlen,

dstadr

 

 

R0

R1

 

R3=адр

R4

R5

(R2=0)

MOVTC

srclen,

srcadr,

fill,

tbladr,

dstlen,

dstadr

 

a - если встречен символ-переключатель, то число оставшихся в строке байт,
включая символ-переключатель; в противном случае 0
б - адрес байта, выборка которого была осуществлена последней,
или адрес байта, следующего за последним байтом строки
в - число байтов, оставшихся в строке-получателе
г - адрес первого немодифицированного байта

Как показывает сравнение с инструкцией MOVTC, символ-заполнитель заменяется символом переключения кода. Поскольку символ-заполнитель отсутствует, инструкция завершается, если в исходной строке исчерпаны все байты; при этом регистр R1 содержит адрес первого байта, следующего за последним байтом строки. Инструкция MOVTUC устанавливает коды условий так же, как инструкция MOVTC (CMPW srclen,dstlen), за исключением того, что бит V устанавливается, если инструкция завершается при извлечении из таблицы символа, соответствующего символу переключения кода.

Инструкции SCANC и SPANC подобны инструкциям LOCC и SKPC, за исключением того, что завершение происходит, когда обнаруживается один из некоторого набора символов (SCANC) или когда обнаруживается символ, не входящий в заданный набор (SPANC). Формат этих инструкций такой:

SCANC

srclen,

srcadr,

tbladr,

mask

 

 

R0

R1

R3=адр

 

(R2=0)

SPANC

srclen,

srcadr,

tbladr,

mask

 

 

R0=a

R1

R3=адр

 

(R2=0)

a - число оставшихся в строке байт, включая байт на котором было
завершено выполнение инструкции
б - адрес байта на котором завершилось выполнение инструкции или
srcadr+srclen

Байты из исходной строки (определённой первыми двумя операндами) последовательно используются как индексы таблицы (определённой третьим операндом). Над каждым байтом, извлечённым из таблицы, выполняется логическая операция И с байтом, заданным четвёртым операндом (маска). Инструкция SCANC завершается, когда результат выполнения операции И отличен от нуля или исчерпаны все символы исходной строки. Инструкция SPANC завершается, когда результат выполнения операции И равен 0 или когда исчерпаны все символы в исходной строке.

Восьмибитовая маска обеспечивает большую гибкость. Например, символы могут быть классифицированы по восьми двоичным атрибутам, таким как цифра или нецифра, прописная буква или любой другой символ, строчная буква или любой другой символ и т.п. Вход в таблицу для каждого символа базируется на восьми двоичных атрибутах. При выборе подходящей маски инструкция SCANC может искать символ, обладающий одним или несколькими из этих атрибутов. (Инструкция SPANC может использоваться для пропуска символов, обладающих одним или несколькими атрибутами.)

УПРАЖНЕНИЯ 13.1

  1. Опишите, как можно реализовать инструкцию MOVC3 таким образом, что не нужна будет временная память для символов, даже если строки перекрываются.
  2. Напишите макроинструкцию, имитирующую инструкцию MOVC3, используя только инструкции, описанные в гл. 8 (ваша макроинструкция должна уметь справляться с перекрывающимися строками). Напишите основную программу, которая тестирует вашу макроинструкцию, сравнивая полученные результаты с результатами инструкции MOVC3.
  3. Напишите программу, которая считывает предложения некоторого текста и создаёт отсортированный список слов из этого текста. Вы можете полагать, что слова в тексте разделяются одним или несколькими пробелами и что нет никаких других знаков пунктуации. Сделайте предположение, что слова не могут быть длиннее 20 символов.
  4. Перепишите программу п. 3 упр. 13.1, полагая, что текст содержит реальную пунктуацию, включая точки, запятые, вопросительные знаки, тире, апострофы и т. п.
  5. Напишите программу, которая считывает текст, как в п. 3 упр. 13.1, и распечатывает текст в виде правильно сформатированного параграфа. (Между словами должно быть только по одному пробелу и в каждой строке должно размешаться максимальное число слов.) Заметим, что входной текст может содержать между словами много пробелов.
  6. Модифицируйте программу п. 5 так, чтобы между словами добавлялось достаточное для выравнивания текста в строчке по правой границе число пробелов.
  7. * Напишите программу, которая считывает любую программу на языке ассемблера и распечатывает все символические имена, встречающиеся в этой программе. (Заметим, что при этом требуется частичный синтаксический разбор программы на языке ассемблера.)
  8. * Скомбинируйте п. 3 и 7 для создания программы, которая распечатывает полную таблицу имён для программы на языке ассемблера (без значений имён).

13.6. АРИФМЕТИКА УПАКОВАННЫХ ДЕСЯТИЧНЫХ ЧИСЕЛ

УПАКОВАННЫЕ ДЕСЯТИЧНЫЕ ЧИСЛА

При обработке экономической информации программисты предпочитают обозначать числа в виде последовательности десятичных цифр фиксированной длины с фиксированной позицией десятичной точки. В языках, предназначенных для обработки экономической информации, таких как Кобол, подобный вид представления чисел используется очень широко. Несколько причин применения десятичных чисел:

  1. Ввод с перфокарт и вывод на печать делается в виде десятичных полей фиксированной длины.
  2. Большинство числовых значений при обработке экономической информации используется в простых вычислениях, и преобразование десятичных чисел в двоичные числа для выполнения вычислений было бы расточительным.
  3. Фиксированное число цифр помогает предотвращать ошибки, такие как выдача чека на 1 000 000.00 долл. на мелкие расходы.
  4. Десятичные дроби, такие как 0.20, не могут быть точно представлены в двоичном виде.
  5. Представление чисел с плавающей точкой может не обеспечить желаемую точность. Например, большинство финансовых расчётов выполняется с округлением до ближайшего цента, и если при таких вычислениях сохраняются дробные части цента, то это может приводить к бухгалтерским ошибкам.

Чтобы избежать этих и других проблем, первые ЭВМ (первого поколения), разрабатываемые для обработки экономической информации, позволяли представлять числа в виде строк десятичных цифр. Как универсальные вычислительные машины, предназначенные как для обработки данных, так и для научных расчётов, ЭВМ семейства VAX включают поддержку десятичных чисел и десятичной арифметики. Основной идеей является упаковка двух десятичных цифр в каждом байте (отсюда и название упакованные десятичные числа). Например, десятичное число 12345678 может быть представлено в четырёх байтах, начиная с адреса ^X1000, так:

Содержимое

Адрес

12

1000

34

1001

56

1002

78

1003

Каждый 8-битовый байт составляется из двух 4-битовых полубайтов, а каждый полубайт содержит десятичную цифру. Например, в байте ^X1000 левый полубайт содержит ^X1, что представляет десятичную цифру 1, а правый полубайт содержит ^X2, что представляет десятичную цифру 2.

Если содержимое байтов ^X1000 - ^X1003 поместить на одной строке, то результат будет таким:

Содержимое

Адрес

78 56 34 12

1000

Поскольку старшие цифры числа (в данном случае 12) запоминаются в байте с меньшим адресом (в данном случае ^X1000), упакованные десятичные числа могут казаться неудобными при чтении дампа памяти. (Обратите внимание, что содержимое длинного слова, начинающегося с адреса ^X1000, есть ^X78563412 и оно является представлением упакованного десятичного числа 12345678.)

Описанное выше представление упакованного десятичного числа должно быть модифицировано так, чтобы зарезервировать место для знака числа. В ЭВМ семейства VAX придерживаются соглашения, что знак числа запоминается в правом полубайте байта с самым старшим адресом. Это означает, что самый старший байт содержит в левом полубайте самую младшую цифру (цифру разряда единиц) и знак в правом полубайте. Хотя в ЭВМ семейства VAX допускается, чтобы положительный знак представлялся шестнадцатеричными цифрами А, С, Е или F, а отрицательный знак - цифрами В или D, предпочтительными являются представление ^XC для плюса и ^XD для минуса. Таким образом, десятичное число 12345678 представляется так:

Содержимое

Адрес

01

1000

23

1001

45

1002

67

1003

1004

В листинге это может выглядеть следующим образом:

Содержимое

Адрес

8С 67 45 23 01

1000

Десятичное число -12345678 было бы представлено в пяти байтах так

Содержимое

Адрес

8D 67 45 23 01

1000

В ЭВМ семейства VAX упакованные десятичные числа задаются двумя атрибутами: начальным адресом числа и длиной. Однако в противоположность символьным строкам, длина упакованного десятичного числа задаётся числом десятичных цифр, а не байтов. Таким образом, длина упакованного десятичного числа 12345678 (или -12345678) есть восемь, хотя это число занимает 5 байтов.

Заметим, что представление в памяти девятизначного упакованного десятичного числа 012345678 точно совпадает с представлением восьмизначного числа 12345678. Оба числа будут представлены в 5 байтах, содержащими 8С, 67, 45, 23 и 01. Однако два числа могут различаться по длине (девять или восемь цифр). В общем случае длина упакованного десятичного числа может изменяться от 0 до 31-й десятичной цифры. Заметим, что любое упакованное десятичное число чётной длины (такое как 12345678) будет иметь незадействованный полубайт в байте старшего порядка (байт с младшим адресом). Десятичная строка нулевой длины состоит из одного байта, содержащего знак в правом полубайте и неиспользованный левый полубайт. Десятичная строка длиной 31 состоит из 16 байтов.

В языке ассемблера для резервирования памяти и инициализации упакованных десятичных чисел используется директива .PACKED. Следующий листинг ассемблера демонстрирует способ использования директивы .PACKED. В первых четырёх строчках определяются упакованные десятичные числа, которые были описаны ранее. В строчке 5 за значением 12345678 следует символическое имя L_PACKE. В результате этого директива .PACKED определяет имя L_PACKE как длину упакованного десятичного числа PACKE. В данном случае имя L_PACKE будет занесено в таблицу имён со значением 8. В строке 6 имя L_PACKF будет занесено в таблицу имён со значением 9. Наконец, в строке 7 показано, как может быть зарезервировано (без инициализации) место в памяти для упакованного десятичного числа PACKG, имеющего то же самое число байтов, что и PACKE.

8C

67

45

23

01

0000

1

PACKA:

.PACKED

12345678

8D

67

45

23

01

0005

2

PACKB:

.PACKED

-12345678

 

 

 

 

0D

000A

3

PACKC:

.PACKED

-0

 

 

 

 

0C

0006

4

PACKD:

.PACKED

+0

8C

67

45

32

01

000C

5

PACKE:

.PACKED

12345678,L_PACKE

8C

67

45

32

01

0011

6

PACKF:

.PACKED

012345678,L_PACKF

 

 

0000001A

0016

7

PACKG:

.BLKB

<L_PACKE+1>/2+1

 

 

 

 

 

001A

8

 

.END

 

ИНСТРУКЦИИ ДЛЯ РАБОТЫ С УПАКОВАННЫМИ ДЕСЯТИЧНЫМИ ЧИСЛАМИ

В архитектуру ЭВМ семейства VAX включены инструкции для пересылки, сравнения и выполнения арифметических операций над упакованными десятичными числами. Форматы инструкций для упакованных десятичных чисел аналогичны форматам инструкций для целых чисел и чисел с плавающей точкой, за исключением того, что должны быть включены операнды, определяющие длину упакованных десятичных чисел. Это такие инструкции:

Мнемоника Операнды Описание

MOVP

len,srcadr,dstadr

dst ← src

CMPP3

len,srcadr,dstadr

tmp ← src - dst

CMPP4

src1len,src1adr,src2len,src2adr

tmp ← src : dst

ADDP4

srclen,srcadr,dstlen,dstadr

dst ← dst + src

SUBP4

srclen,srcadr,dstlen,dstadr

dst ← dst - src

ADDP6

src1len,src1adr,src2len,src2adr,dstlen,dstadr

dst ← src2 + src1

SUBP6

src1len,src1adr,src2len,src2adr,dstlen,dstadr

dst ← src2 - src1

MULP

src1len,src1adr,src2len,src2adr,dstlen,dstadr

dst ← src2 * src1

DIVP

src1len,src1adr,src2len,src2adr,dstlen,dstadr

dst ← src2 / src1

Инструкции для упакованных десятичных чисел подразумевают использование регистров общего назначения от R0 до R5 точно так же. как и в инструкциях обработки символьных строк. Регистры R0, R2 и (если необходимо) R4 используются для запоминания длины упакованных десятичных чисел, а регистры R1, R3 и (если необходимо) R5 - для адресов этих чисел. После выполнения инструкции регистры с чётными номерами содержат нуль, а регистры с нечётными номерами содержат адреса начала соответствующих десятичных чисел. Например, после выполнения инструкции MOVP (переслать упакованное десятичное) регистры R0 и R2 содержат нуль, регистры R1 и R3 указывают на адреса операндов источника и получателя, а регистры R4 и R5 не используются и не модифицируются. Адреса в регистрах с нечётными номерами могут применяться в качестве входной информации следующей инструкции.

Эти инструкции устанавливают коды условий N, Z и V вполне предполагаемым образом. Бит V устанавливается, если происходит переполнение десятичного числа. Поскольку не существует упакованных десятичных чисел без знака, бит С просто сбрасывается, за исключением инструкции MOVP, которая сохраняет значение бита С.

Имеется одна тонкость в отношении установки битов N и Z. В зависимости от способа представления в упакованном десятичном числе знакового разряда, существует упакованное десятичное представление как +0, так и -0 (так как длина упакованного десятичного числа может изменяться от 0 до 31, фактически существует 64 различных представления нуля). Существование представлений как для +0, так и для -0 в некоторых ЭВМ более старой архитектуры порождало трудности (инструкция перехода по условию "если меньше чем 0" могла бы вызвать переход при появлении числа -0, но переход не будет осуществлён в случае числа +0, хотя обе эти величины математически эквивалентны). Чтобы избежать этого в архитектуре ЭВМ семейства VAX приняты два решения. Во-первых, нулевой результат всегда представляется как +0. Например, если исходным операндом инструкции MOVP является -0, то в операнд-получатель будет переслан +0. Но существует возможность получения результата -0, если происходит переполнение десятичного числа. Вторая особенность решает эту проблему. Если результат есть -0, ЭВМ семейства VAX просто устанавливает биты N и Z так, как если бы результат был +0. Таким образом, при получении результата +0 или -0 биты будет сбрасываться и установится бит Z. Поэтому программистам не нужно беспокоиться о существовании представления числа -0, за исключением весьма необычных обстоятельств.

ИНСТРУКЦИИ ASHP, CVTLP И CVTPL

Чтобы можно было обрабатывать дробные значения, упакованные десятичные числа рассматриваются как числа с фиксированной точкой. Как пояснялось в гл. 10, это означает, что нахождение десятичной точки подразумевается в некоторой фиксированной позиции внутри числа. Следить за позицией подразумеваемой десятичной точки - обязанность программиста. В частности, расположение десятичных точек должно выравниваться перед сложением или вычитанием. (Как мы видели в гл. 10, та же самая проблема возникает при вычислениях с плавающей точкой. Однако необходимое там выравнивание осуществляется автоматически инструкциями для чисел с плавающей точкой.) Выравнивание упакованных десятичных чисел может выполняться путём умножения или деления чисел на степени числа 10. Однако умножение или деление упакованного десятичного числа можно быстрее выполнить с помощью сдвига полубайтов. Такой сдвиг выполняется инструкцией ASHP (сдвинуть арифметически упакованное десятичное число).

Инструкция ASHP требует шесть операндов. В дополнение к длинам и адресам для исходной и целевой строки первый операнд инструкции, имеющий формат байта, задаёт величину сдвига в десятичных разрядах. Положительное значение этого операнда приводит к эффективному умножению исходного операнда на 10n и размещению результата в целевой строке. Отрицательное значение приводит к делению исходного операнда на 10n. Четвёртый операнд - это операнд, имеющий формат байта, который позволяет выполнять деление с округлением вместо усечения. Например, следующая инструкция преобразует упакованное десятичное число, соответствующее величине, выраженной в долларах, в число, соответствующее величине, выраженной в центах:

ASHP    #2,#DOLLAR_LEN,DOLLAR,#0,#CENTS_LEN,CENTS

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

ASHP    #-2,#CENTS_LEN,CENTS,#0,#DOLLAR_LEN,DOLLAR

Эта инструкция осуществит усечение при пересылке упакованного десятичного числа CENTS в DOLLAR. Округление может быть получено с помощью инструкции:

ASHP    #-2,#CENTS_LEN,CENTS,#5,#DOLLAR_LEN,DOLLAR

Четвёртый операнд #5 перед делением на 100 прибавляет 50 к упакованному десятичному числу, расположенному по адресу CENTS. Инструкция ASHP использует регистры R0 - R3, как это ожидается.

Инструкция MOVP предназначена для пересылки исходного упакованного числа в область памяти, занимающую то же число байтов. Для пересылки упакованных десятичных чисел между полями разной длины может использоваться инструкция ASHP (с операндами сдвига и округления, равными 0). Например, инструкция

ASHP    #0,#4,SOURCE,#0,#10,DEST

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

В архитектуре ЭВМ семейства VAX имеется одна пара инструкций для преобразования упакованных десятичных чисел в целые числа со знаком и обратно. Это инструкции CVTLP (преобразовать длинное слово в упакованное) и CVTPL (преобразовать упакованное число в длинное слово). Описание этих инструкций:

CVTLP

longsrc,

dstlen,

dstadr

 

 

 

R2=0

R3=a

R0=0, R1=0

CVTPL

srclen,

srcadr,

longdst

 

 

R0=0

R1=a

 

R2=0, R3=0

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

Инструкция преобразования длинного слова легко может быть использована для преобразования между целыми числами в формате байта и слова и упакованными десятичными числами. Например, следующие инструкции преобразуют 16-битовое целое со знаком, расположенное по адресу WORD, в пятизначное упакованное десятичное число:

CVTWL   WORD,LONG           ; ПРЕОБРАЗОВАТЬ СЛОВО В ДЛИННОЕ СЛОВО
CVTLP   LONG,#5,PACK_ADR    ; ПРЕОБРАЗОВАТЬ ДЛ. СЛОВО В УПАКОВ. ДЕСЯТИЧНОЕ

Обратите внимание, что переполнения никогда не произойдёт, поскольку 16-битовое целое со знаком всегда помещается в пятизначном десятичном поле. Аналогично следующие операторы будут преобразовывать пятизначное упакованное десятичное число в 16-битовое двоичное целое. Заметим, что возможно переполнение.

CVTPL   #5,PACK_ADR,LONG    ; ПРЕОБРАЗОВАТЬ УПАКОВ. ДЕСЯТИЧНОЕ В ДЛ. СЛОВО
CVTLW   LONG,WORD           ; ПРЕОБРАЗОВАТЬ ДЛИННОЕ СЛОВО В СЛОВО
                            ; (ВОЗМОЖНО ПЕРЕПОЛНЕНИЕ)

Преобразование между упакованными десятичными числами и целыми со знаком в формате квадраслова является более сложным. Следующие операторы преобразовывают целое со знаком в формате квадраслова с адресом QUAD в двадцатизначное упакованное десятичное число с адресом PACKED_ANS:

PACKED_CONST:
        .PACKED     4294967286              ; КОНСТАНТА 2**32
        . . .
        MOVL        QUAD,LONG_LOW           ; РАЗБИТЬ НА ДВА ДЛИННЫХ СЛОВА
        MOVL        QUAD+4,LONG_HIGH
        CVTLP       LONG_LOW,#10,PACKED_LOW ; ПРЕОБРАЗОВАТЬ В ДЕСЯТИЧНОЕ
        CVTLP       LONG_HIGH,#10,PACKED_HIGH
; ПОМЕСТИТЬ ДВА УПАКОВ. ДЕСЯТИЧНЫХ ДЛИНОЙ 10 В PACKED_ANS С ДЛИНОЙ 20
        MULP        #10,PACKED_CONST,#10,PACKED_HIGH,#20,PACKED_ANS
        ADDP4       #10,PACKED_LOW,#20,PACKED_ANS
        TSTL        LONG_LOW
        BGEQ        10$
        ADDP4       #10,PACKED_CONST,#10,PACKED_ANS
10$: . . .

Первые две инструкции разбивают 64-битовое квадраслово на два 32-битовых длинных слова LONG_LOW и LONG_HIGH. Если оба длинных слова положительны (т.е. бит 31 нулевой), то преобразование очевидно. Длинные слова преобразуются в десятизначные упакованные десятичные числа, упакованное десятичное число, выражающее старшие порядки, умножается на 232 и пересылается в PACKED_ANS, а упакованное десятичное число, выражающее младшие порядки, прибавляется к PACKED_ANS.

Если одно или оба числа в формате длинного слова отрицательны, алгоритм более сложен. Например, предположим, что бит 31 в LONG_LOW равен 1, а бит 31 в LONG_HIGH нулевой. Бит 31 в LONG_LOW представляет 231-битовую позицию в 64-битовом квадраслове. Однако инструкция CVTLP обрабатывает этот бит как знаковый, и операнд PACKED_LOW становится отрицательным числом. После выполнения первой инструкции ADDP4 ответ неверен по двум причинам. Во-первых, число в PACKED_LOW в действительности вычитается из числа в PACKED_ANS, а не прибавляется к нему. Во-вторых, поскольку бит 31 на самом деле является битом данных, но не обрабатывается как бит данных, необходимо прибавить к ответу число 231. Ситуация такова, что комбинированное влияние этих двух факторов всегда даёт в результате ответ, который меньше правильного на 232. Последние три инструкции прибавляют 232 к ответу, если число в PACKED_LOW отрицательно. В качестве упражнения предлагаем читателю продемонстрировать работу этого алгоритма для ряда чисел.

13.7. ЧИСЛОВЫЕ СТРОКИ

ФОРМАТЫ ЧИСЛОВЫХ СТРОК

До сих пор было рассмотрено четыре различных представления чисел: двоичные целые без знака, двоичные целые со знаком, вещественные или числа с плавающей точкой и упакованные десятичные числа. Арифметические вычисления могут выполняться над числами в любом из этих представлений. Кроме того, числа, имеющие любое из этих представлений, могут быть записаны или считаны с внешних запоминающих устройств, таких как магнитный диск или магнитная лента. Однако они не могут быть выведены или считаны с устройств, использующих символьное представление информации в коде ASCII, таких как клавиатура терминалов, экраны дисплеев или устройства печати. Подобные устройства требуют, чтобы числа были представлены в виде строк символов в коде ASCII.

Для того чтобы поддерживать работу устройств, использующих символьный формат, в архитектуру ЭВМ семейства VAX включено пятое представление чисел, называемое числовыми строками. По существу числовая строка - это не что иное, как символьная строка, в которой символы ограничены набором цифр в коде ASCII от 0 до 9. Другими словами, содержимое байтов в строке ограничивается значениями цифр в коде ASCII, а именно от ^X30 до ^X39.

Числа представляются в виде символьных строк со времени появления ЭВМ. Вследствие различий между ЭВМ (включая различия в используемых символьных кодах) был разработан ряд представлений знака числа. Поэтому архитектура ЭВМ семейства VAX поддерживает несколько методов представления знака числовой строки. Самое простое представление - это числовая строка с раздельным ведущим знаком. Название этого представления дано вследствие того, что код ASCII для знака плюс (^X2B) или знака минус (^X2D) запоминается в отдельном байте перед цифрой старшего порядка числа. Например, ниже показано, как можно определить несколько числовых строк с раздельным ведущим знаком в языке ассемблера:

STRING_1:   .ASCII  "+1234"         ; 5 БАЙТ, НО ДЛИНА ЧИСЛОВОЙ СТРОКИ 4
STRING_2:   .ASCII  "-12345678"     ; 9 БАЙТ, НО ДЛИНА  ЧИСЛОВОЙ СТРОКИ 8
STRING_3:   .ASCII  "+"             ; 1 БАЙТ, НО ДЛИНА ЧИСЛОВОЙ СТРОКИ 0
STRING_4:   .ASCII  "-"             ; ПРЕДСТАВЛЕНИЕ "ОТРИЦАТЕЛЬНОГО НУЛЯ"
STRING_5:   .ASCII  "-0000"         ; ТОЖЕ, НО ДЛИНА ЧИСЛОВОЙ СТРОКИ 4
STRING_6:   .ASCII  " 1234"         ; АНАЛОГИЧНО STRING_1,
                                    ; НО С ПРОБЕЛОМ ДЛЯ ЗНАКА

Числовая строка с раздельным ведущим знаком задаётся двумя атрибутами: адресом начала строки (который в действительности является адресом знакового байта) и длиной строки. Однако длина определяет число цифр в строке, которое из-за знакового байта на 1 меньше числа байтов в строке. Как показано в шестой строке, знак плюс может быть заменён символом пробела (^X20). Однако использование знака "плюс" предпочтительнее.

Правила для числовых строк с раздельным ведущим знаком весьма строги. Например, каждое из следующих представлении недопустимо по указанной причине:

BAD_1:      .ASCII  "1234"          ; ОТСУТСТВУЕТ ЗНАК
BAD_2:      .ASCII  ""              ; ТРЕБУЕТСЯ ЗНАК
BAD_3:      .ASCII  " +1234"        ; НЕДОПУСТИМ ПРОБЕЛ ПЕРЕД ЗНАКОМ
BAD_4:      .ASCII  "+123,456"      ; В СТРОКЕ НЕДОПУСТИМА ЗАПЯТАЯ
BAD_5:      .ASCII  "-1234.56"      ; ТОЧКА НЕДОПУСТИМА

Чтобы сохранить согласование с упакованными десятичными числами, длина числовой строки с раздельным ведущим знаком должна быть от 0 до 31 цифры (из-за знакового байта число байтов изменяется от 1 до 32).

На ЭВМ семейства VAX поддерживается второй формат для числовых строк, называемый числовыми строками с ведомым знаком. Этот формат был образован от кода Холлерита для перфокарт. Как отмечалось в гл. 9, перфокарта холлеритовского стандарта содержит 12 рядов, называемых соответственно рядами 12, 11, 0, 1, 2, . . ., 8 и 9. Пробивки в рядах 0 - 9 используются для представления цифр от 0 до 9, пробивка в ряду 12 обозначает знак "плюс", а пробивка в ряду 11 обозначает знак "минус". Вместо того чтобы отводить под знак отдельный столбец, знак включается в тот же столбец, где находится и цифра разряда единиц. Например, для представления числа -12345 в первых четырёх столбцах делаются соответственно пробивки в рядах 1, 2, 3 и 4, а в пятом столбце делается две пробивки в рядах 5 и 11. Поскольку пробивка знака располагается в одним столбце с цифрой разряда единиц, это представление называют форматом с дополнительной пробивкой или зонным форматом 2.

Когда перфокарточные коды были расширены включением алфавитных символов, букве N был назначен перфокарточный код, состоящий из комбинации пробивок в рядах 11 и 5. Чтобы ввести число -12345, опытный оператор перфоратора может напечатать 1234N. Аналогично, поскольку код буквы Е состоит из комбинации пробивок 12 и 5, число +12345 может быть введено на перфораторе как 1234Е.

В результате следующие представления числовых строк с ведомым знаком допустимы в ЭВМ семейства VAX:

TRAIL_1: .ASCII  "1234N"    ; +12345 В ЗОННОМ ФОРМАТЕ 2
TRAIL_2: .ASCII  "1234Е"    ; -12345 В ЗОННОМ ФОРМАТЕ 2

В этом формате последний байт содержит как знак, так и цифру разряда единиц. Левый полубайт байта определяет знак числа, а правый полубайт - цифру разряда единиц. Для большей совместимости с представлением символов в коде EBCDIC (см. гл. 8) ЭВМ семейства VAX поддерживают также второй формат для числовых строк с ведомым знаком, называемый зонным числовым форматом или зонным форматом 1. В этом формате для представления знаков "плюс" и "минус" просто используются различные битовые комбинации в левом полубайте последнего байта.

Чтобы справляться при обработке с различными способами кодирования знака и цифры разряда единиц в числовых строках с ведомым знаком, в инструкциях ЭВМ семейства VAX для обработки числовых строк с ведомым знаком используется 256-байтовая таблица для обработки байта, содержащего цифру разряда единиц и знак. За более подробной информацией по форматам числовых строк с ведомым знаком заинтересованного читателя отсылаем к книге "VAX Architecture Handbook". В оставшейся части раздела будут использоваться более лёгкие для понимания числовые строки с раздельным ведущим знаком.

ИНСТРУКЦИИ ПРЕОБРАЗОВАНИЯ ЧИСЛОВЫХ СТРОК

В архитектуре ЭВМ семейства VAX нет каких-либо инструкций для выполнения арифметических операций над числовыми строками. Однако в неё входят инструкции для преобразования числовых строк в упакованные десятичные числа и обратно. Это инструкции CVTPS (преобразовать упакованное десятичное в числовую строку с ведущим знаком) и CVTSP (преобразовать числовую строку с ведущим знаком в упакованное десятичное). Описание этих инструкций:

CVTPS

src_len,

src_adr,

dst_len,

dst_adr

 

R0=0

R1=a

R2=0

R3=a

CVTSP

src_len,

src_adr,

dst_len,

dst_adr

 

R0=0

R1=a

R2=0

R3=a

a - адрес байта, следующего за последним байтом строки

Биты N и Z устанавливаются в зависимости от знака и значения упакованного десятичного операнда. Бит V устанавливается, если происходит переполнение, а бит C всегда сбрасывается.

В архитектуру ЭВМ семейства VAX включены также инструкции для преобразования числовых строк с ведомым знаком. Инструкции CVTPT (преобразовать упакованное десятичное в числовую строку с ведомым знаком) и CVTTP (преобразовать числовую строку с ведомым знаком в упакованное десятичное) имеют аналогичный формат операндов и работают аналогично инструкциям CVTPS и CVTSP. Поскольку существует ряд способов представления знака для числовых строк с ведомым знаком инструкции CVTPT и CVTTP имеют дополнительный операнд. Этот операнд задаёт адрес 256-байтовой таблицы, которая определяет представление знака. (См. детали в книге "VAX Architecture Handbook".)

Заметим, что инструкция CVTSP будет завершаться по ошибке использования резервного операнда, если исходная строка содержит недопустимый символ. Исходная строка должна содержать код ASCII для знаков "плюс", "минус" или "пробел" в первом байте и коды ASCII для цифр 0 - 9 во всех последующих байтах. Если подлежащие преобразованию числа содержат ведущие пробелы, для их пропуска можно воспользоваться инструкцией SKPC.

ИНСТРУКЦИЯ EDITPC

Программист может иметь желание писать числа в самых разнообразных форматах. Предположим, например, что в поле упакованного десятичного числа содержится число 12345. Предположим далее, что это число должно быть преобразовано в символьную строку, содержащую десять байтов. Следующий список показывает различные возможные форматы получаемой в результате символьной строки. Символ подчёркивания (_) используется для обозначения символа пробела.

+000012345

_____12345

0000012345

____+12345

$000012345

$____12345

____$12345

$****12345

0012345 CR

0000123.45

____123.45

___+123.45

$***123.45

0123.45 CR

$___123.45

___$123.45

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

Для формирования первой выведенной строки (+000012345) может быть использована инструкция CVTPS. Кроме того, для формирования различных символьных строк могут быть полезны различные инструкции преобразования кода символов. Однако проблема на самом деле сложнее, чем может показаться. Рассмотрим, например, формат, по которому образуется символьная строка ____123.45. Если число, подлежащее преобразованию в символьную строку, есть нуль, то результат ______0.00 предпочтительнее результата _______.__.

Чтобы упростить задачу преобразования упакованных десятичных чисел в символьные строки, в архитектуру ЭВМ семейства VAX включена инструкция EDITPC (отредактировать упакованное десятичное). Формат этой инструкции:

EDITPC

srclen,

srcadr,

pattern,

dstadr

 

 

R0=длина

R1=a

R3

R5

(R2=R4=0)

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

Первые два операнда - это длина и адрес упакованного десятичного числа. Третий операнд - шаблон, представляет начальный адрес последовательности операторов шаблона редактирования. Каждый оператор шаблона редактирования - это один или два байта, показывающие, как должны располагаться символы в формируемой символьной строке. Последний операнд - это адрес начала символьной строки. Заметим, что длина символьной строки не является операндом. Так сделано потому, что длина упакованных десятичных чисел вместе с операторами шаблона редактирования однозначно определяют длину символьной строки - результата.

Точное кодирование операторов шаблона редактирования описано в книге "VAX Architecture Handbook". Для облегчения использования операторов шаблона редактирования в макробиблиотеке ЭВМ семейства VAX содержится набор макроинструкций для образования операторов шаблона. Рассмотрим, например, следующее:

PACKED:     .PACKED         0012345
PATTERN:
            EO$LOAD_FILL    #^A" "
            EO$MOVE         7
            EO$END
CHAR_7:     .BLKB           7
            . . .
            EDITPC          #7,PACKED,PATTERN,CHAR_7

Шаблон содержит три макроинструкции: EO$LOAD_FILL, EO$MOVE и EO$END. Сначала опишем действие макроинструкции EO$MOVE. Она образует оператор шаблона, называемый EO$MOVE, который приводит к тому, что некоторое число десятичных цифр извлекается из упакованного десятичного числа, преобразуется в символы кода ASCII и запоминается в целевой строке. В данном случае как аргумент указано число 7, так что все семь цифр из упакованного десятичного PACKED будут преобразованы. В результате можно ожидать, что в CHAR_7 будет запомнена строка 0012345.

Однако во многих случаях желательно подавить ведущие нули в начале числа. Чтобы это сделать, инструкция EDITPC использует однобитовую область памяти, называемую битом значимости. Пока извлекаемые из упакованного десятичного числа цифры остаются нулями, бит значимости сброшен. Но как только извлекается цифра, отличная от нуля, бит значимости устанавливается (устанавливается значимость) и остаётся установленным, даже если в последующем будут ещё извлекаться нули (эти нули значащие, так как они следуют за ненулевой цифрой). Инструкция EDITPC использует также 8-битовый регистр, который содержит символ, называемый символом-заполнителем. Назначением макроинструкции EO$LOAD_FILL является загрузка символа-заполнителя в регистр заполнения.

Оператор EO$LOAD_FILL в предыдущем примере загружает в регистр заполнения символ пробела. (Операнд, указанный в макроинструкции, есть #^A"*". Символ # задаёт непосредственный операнд, префикс ^A показывает, что непосредственный операнд определяется как символ кода ASCII, а " " обозначает, что задан символ пробела.) Оператор EO$MOVE извлекает цифры из исходного упакованного десятичного числа так, как описано выше. Однако, если бит значимости не установлен (поскольку не была извлечена отличная от нуля цифра), цифры из исходной строки замещаются символом-заполнителем (в данном случае - пробелом). В результате после завершения выполнения, по адресу CHAR_7 будет находиться строка __12345. Заметим, что если внести изменение в оператор EO$LOAD_FILL #^A"*", то в результате будет образована строка **12345.

Если бы число PACKED было 0000000, бит значимости никогда бы не установился, символ-заполнитель заменил бы все семь цифр числа и результат был бы _______. Чтобы скорректировать это, следует изменить шаблон:

PATTERN:
        EO$LOAD_FILL    #^A" "  ;СИМВОЛ-ЗАПОЛНИТЕЛЬ ПРОБЕЛ
        EO$MOVE         6       ;ДЛИНА ПОЛЯ 6 РАЗРЯДОВ, В СВОБОДНЫЕ
                                ;РАЗРЯДЫ ПОМЕЩАЕТСЯ ЗАПОЛНИТЕЛЬ
        EO$SET_SIGNIF           ;ВЫБИРАЕТСЯ НЕ НУЛЕВОЙ РАЗРЯД
        EO$MOVE         1       ;ОБЯЗАТЕЛЬНО ПРЕОБРАЗОВАТЬ СЕДЬМОЙ РАЗРЯД
        EO$END

Макроинструкция EO$SET_SIGNIF устанавливает бит значимости, даже если все цифры, извлечённые из исходной строки, были нулями. В результате этот шаблон даёт такой же эффект, как и предыдущий, за исключением того, что нулевое исходное число преобразуется в ______0.

Для того чтобы позволить распечатывать знак числа, инструкция EDITPC использует второй 8-битовый регистр, предназначенный для хранения символа знака. Этот знаковый регистр подобен регистру заполнения, за исключением того, что для загрузки символа в знаковый регистр используются другие операторы шаблона. Макроинструкция EO$LOAD_SIGN осуществляет безусловную загрузку знакового регистра, макроинструкция EO$LOAD_PLUS загружает знаковый регистр тогда, и только тогда, когда упакованное десятичное число положительное, а макроинструкция EO$LOAD_MINUS загружает знаковый регистр, если упакованное десятичное отрицательно. Оператор EO$STORE_SIGN используется для вставки знака числа в формируемую строку. Рассмотрим, например, следующее:

PACKED: .PACKED 0012345
PATTERN:
        EO$LOAD_FILL    #^A" "      ;СИМВОЛ-ЗАПОЛНИТЕЛЬ ПРОБЕЛ
        EO$LOAD_PLUS    #^A"+"      ;ЕСЛИ ДЕСЯТИЧНОЕ > 0, TO ДОБАВИТЬ ЗНАК "+"
        EO$LOAD_MINUS   #^A"-"      ;ЕСЛИ ДЕСЯТИЧНОЕ < 0, TO ДОБАВИТЬ ЗНАК "-"
        EO$STORE_SIGN               ;ЗНАКОВЫЙ РАЗРЯД ОБЯЗАТЕЛЕН
        EO$MOVE         6           ;ДЛИНА ПОЛЯ 6 РАЗРЯДОВ, В СВОБОДНЫЕ
                                    ;РАЗРЯДЫ ПОМЕЩАЕТСЯ ЗАПОЛНИТЕЛЬ
        EO$SET_SIGNIF               ;УСТАНОВИТЬ ЗНАЧИМОСТЬ, ЕСЛИ ОНА ОТСУТСТВУЕТ
        EO$MOVE         1           ;ОБЯЗАТЕЛЬНО ПРЕОБРАЗОВАТЬ ПОСЛЕДНИЙ РАЗРЯД
        EO$END
CHAR_7: .BLKB           8
        . . .
        EDITPC          #7,PACKED,PATTERN,CHAR_7

Будет порождена строка +__12345. Заметим, что если бы упакованное десятичное число было нулём, результат был бы +______0.

Макроинструкции EO$FLOAT и EO$END_FLOAT используются для перемещения знака вправо так, чтобы он оказался перед первой значащей цифрой числа. Макроинструкция EO$FLOAT - такая же, как и EO$MOVE, за исключением того, что когда обнаруживается первая значащая цифра, то перед цифрой помещается знак числа. Для обработки случая, когда не найдено ни одной значащей цифры, последовательность из одного или нескольких операторов EO$FLOAT должна завершаться одним оператором EO$END_FLOAT, который помещает знак числа в строку, только, если знак не был уже помещён в строку оператором EO$FLOAT. Например, если предыдущую строку шаблона заменить на следующую строку, будет сформирована строка __+12345. Если упакованное число было нулевым, сформировано будет ______+0.

EO$LOAD_FILL    #^A" "      ;СИМВОЛ-ЗАПОЛНИТЕЛЬ ПРОБЕЛ
EO$LOAD_PLUS    #^A"+"      ;ЕСЛИ ДЕСЯТИЧНОЕ >0, TO ДОБАВИТЬ ЗНАК "+"
EO$LOAD_MINUS   #^A"-"      ;ЕСЛИ ДЕСЯТИЧНОЕ <0, TO ДОБАВИТЬ ЗНАК "-"
EO$FLOAT        6           ;ДЛИНА ПОЛЯ 6 РАЗРЯДОВ, В СВОБОДНЫЕ
                            ;РАЗРЯДЫ ПОМЕЩАЕТСЯ ЗАПОЛНИТЕЛЬ
EO$END_FLOAT    6           ;ВЫВЕСТИ ЗНАК, ЕСЛИ ТРЕБУЕТСЯ
EO$SET_SIGNIF               ;УСТАНОВИТЬ ЗНАЧИМОСТЬ, ЕСЛИ ОНА ОТСУТСТВУЕТ
EO$MOVE         1           ;ОБЯЗАТЕЛЬНО ПРЕОБРАЗОВАТЬ ПОСЛЕДНИЙ РАЗРЯД
EO$END

Оператор шаблона EO$INSERT может использоваться для занесения в формируемую строку какого-то конкретного символа (операндом является вставляемый символ). Таким образом, оператор EO$INSERT может быть использован для занесения в выходную строку запятых (1,234,567), десятичных точек (12345.67) или того и другого (12,345.67). Если не обнаружено значащих цифр, этот оператор вставляет символ-заполнитель, так что нуль может быть распечатан без ненужных запятых (______0, а не__,____0). (Если оператору EO$INSERT предшествует оператор EO$SET_SIGNIF, то символ-заполнитель будет вставлен безусловно.) Дополнительную информацию по этим и другим операторам шаблона см. в книге "VAX Architecture Handbook".

13.8. БИТОВЫЕ ПОЛЯ ПЕРЕМЕННОЙ ДЛИНЫ

Иногда желательно адресовать группы битов, которые не обязательно начинаются с границы байта. Предположим, например, что телевизионное изображение высокого разрешения преобразуется в цифровую форму в виде матрицы 1000 на 1000 элементов, где каждый элемент матрицы - это 10-битовое число, показывающее уровень яркости в данной точке. Каждое 10-битовое число может быть представлено в 16-битовом слове, но при этом остались бы неиспользованными 6 битов. Однако неиспользованные биты составляют 6 млн. битов, или 750 тыс. байтов потерянной памяти.

Чтобы избежать потери битов, 10-битовые числа могут быть упакованы вместе следующим образом:

   

Элемент 3

Элемент 2

Элемент 1

b

b

b

b

b

b

b

b

b

b

b

b

b

b

b

b

b

b

b

b

b

b

b

b

b

b

b

b

b

b

b

b

Байт 4

Байт 3

Байт 2

Байт 1

Выборку и занесение 10-битовых полей с помощью описанных до сих пор инструкций делать неудобно.

Для решения этой и других проблем в архитектуру ЭВМ семейства VAX включен тип данных, называемый битовым полем переменной длины. Битовое поле задаётся тремя атрибутами:

  1. Адрес байта, который определяет адрес начала области памяти, используемой для битовых полей (адрес байта 1 в примере).
  2. Битовое смещение, которое определяет интервал в битах между адресом начала области памяти и адресом начала битового поля (0 для элемента 1, 10 для элемента 2, 20 для элемента 3 и т.д. в примере).
  3. Длина поля, которая определяет длину битового поля в битах (10 в примере).

Начальный адрес таблицы может быть задан любым режимом адресации, который даёт 32-битовый адрес (кроме того, может использоваться регистровая адресация). Это означает, что группа битов может находиться в одном из регистров. Битовое смещение задаётся в формате 32-битового длинного слова как целое со знаком. Заметим, что это смещение можно представлять как 29-битовое байтовое смещение, определяющее байт, в котором начинается битовое поле, за которым следует 3-битовое число, задающее местоположение этого поля внутри байта. Длина поля - это целое без знака в формате байта, значение которого находится в диапазоне 0 - 32. Если поле находится в регистре общего назначения, то значение смещения битового поля должно быть в диапазоне 0 - 31.

В архитектуру ЭВМ семейства VAX включены инструкции для извлечения (выборки), сравнения и вставки (занесения) битовых полей. Форматы этих инструкций:

EXTV    disp,len,start,longdst
CMPV    disp,len,start,longsrc
INSV    longsrc,disp,len,start

В полях операндов обозначение start определяет адрес начала блока памяти, используемого для хранения битовых полей, disp определяет 32-битовое смещение, a len - это байтовый операнд, определяющий длину поля. Инструкция EXTV извлекает группу битов и помещает её в длинное слово (longdst), причём младший бит группы попадает в младший бит длинного слова. Битовое поле рассматривается как число со знаком, так что значение старшего бита поля распространяется по старшим битам длинного слова (альтернативная инструкция EXTZV вместо знакового бита распространяет нуль и поэтому используется для битовых полей, интерпретируемых как числа без знака). Аналогичным образом инструкция CMPV расширяет битовое поле, приводя его к формату длинного слова, перед сравнением с содержимым длинного слова (инструкция CMPZV распространяет нуль вместо знакового бита). Биты кодов условий устанавливаются так, как и ожидается, за исключением того, что инструкция INSV оставляет коды условий без изменения.

Работа этих инструкций очевидна. В следующем примере инструкция EXTV извлекает биты с 20-й по 25-й из регистра R2 и помещает результат с распространённым значением знакового разряда в регистр R4. Инструкция INSV помещает 6-битовую строку в битовые позиции 15-20 регистра R10.

EXTV    #20,#6,R2,R4
INSV    R4,#15,#6,R10

13.9. ОЧЕРЕДИ И ИНСТРУКЦИИ ДЛЯ РАБОТЫ С НИМИ

Архитектура ЭВМ семейства VAX поддерживает структуры данных, называемых абсолютными очередями. Очередь - это разновидность двунаправленного списка, в котором каждый элемент очереди содержит указатель на предыдущий элемент в этом списке, а также указатель на последующий элемент списка. В очереди последний элемент списка указывает на первый элемент списка, формируя тем самым циклическую структуру.

Рассмотрим, например, элементы HEAD, A, B, C и D.

HEAD:   .ADDRESS    C       ;ССЫЛКА ВПЕРЁД НА ПОСЛЕДУЮЩИЙ ЭЛЕМЕНТ
        .ADDRESS    B       ;ССЫЛКА НАЗАД НА ПРЕДЫДУЩИЙ ЭЛЕМЕНТ
        . . .
A:      .ADDRESS    D       ;ССЫЛКА ВПЕРЁД НА ПОСЛЕДУЮЩИЙ ЭЛЕМЕНТ
        .ADDRESS    C       ;ССЫЛКА НАЗАД НА ПРЕДЫДУЩИЙ ЭЛЕМЕНТ
        . . .
B:      .ADDRESS    HEAD    ;ССЫЛКА ВПЕРЁД НА ПОСЛЕДУЮЩИЙ ЭЛЕМЕНТ
        .ADDRESS    D       ;ССЫЛКА НАЗАД НА ПРЕДЫДУЩИЙ ЭЛЕМЕНТ
        . . .
C:      .ADDRESS    A       ;ССЫЛКА ВПЕРЁД НА ПОСЛЕДУЮЩИЙ ЭЛЕМЕНТ
        .ADDRESS    HEAD    ;ССЫЛКА НАЗАД НА ПРЕДЫДУЩИЙ ЭЛЕМЕНТ
        . . .
D:      .ADDRESS    B       ;ССЫЛКА ВПЕРЁД НА ПОСЛЕДУЮЩИЙ ЭЛЕМЕНТ
        .ADDRESS    A       ;ССЫЛКА НАЗАД НА ПРЕДЫДУЩИЙ ЭЛЕМЕНТ

Каждый элемент начинается с двух адресов, заданных в формате длинного слова (любые данные, относящиеся к этому элементу, могут располагаться вслед за этими двумя адресами). Первый адрес в каждой паре - это указатель на последующий элемент. Поскольку адрес HEAD содержит адрес C, элемент C следует за элементом HEAD. Аналогично адрес C содержит адрес следующего элемента A, так что элемент A следует за элементом C. В прямом направлении порядок следования элементов таков: HEAD, C, A, D, B и затем - обратно HEAD. Второй адрес в каждом элементе указывает на предыдущий элемент. Поскольку по адресу HEAD+4 содержится адрес предшествующего элемента, элемент B предшествует элементу HEAD. В обратном направлении порядок такой: B, D, A, C, HEAD.

Эта структура может быть представлена следующим образом:

Набор инструкций ЭВМ семейства VAX содержит инструкции INSQUE (вставить элемент в очередь) и REMQUE (удалить элемент из очереди). Описание этих инструкций:

INSQUE  elemadr,headadr
REMQUE  headadr,elemadr

Например, инструкция INSQUE A,HEAD вставит элемент A как следующий за элементом HEAD (элемент, следовавший ранее за элементом HEAD, будет следовать за элементом A) Инструкция REMQUE HEAD,E удалит элемент HEAD из очереди и поместит адрес элемента в длинное слово E. Например, показанная выше структура из пяти элементов может быть создана так:

HEAD:   .ADDRESS    HEAD
        .ADDRESS    HEAD
        . . .
A:      .BLKL       2
        . . .
B:      .BLKL       2
        . . .
C:      .BLKL       2
        . . .
D:      .BLKL       2
        . . .
        INSQUE      B,HEAD      ; ОЧЕРЕДЬ СОДЕРЖИТ HEAD, B 
        INSQUE      D,HEAD      ; ОЧЕРЕДЬ СОДЕРЖИТ HEAD, D, B
        INSQUE      A,HEAD      ; ОЧЕРЕДЬ СОДЕРЖИТ HEAD, A, D, B
        INSQUE      C,HEAD      ; ОЧЕРЕДЬ СОДЕРЖИТ HEAD, C, A, D, B

Обратите внимание, что пустая очередь состоит из одного головного элемента (HEAD), в котором оба адреса указывают на головной элемент. Структура из пяти элементов HEAD, C, A, D, B обычно рассматривается как имеющая головной элемент и четыре элемента данных.

Хотя головной элемент рассматривается как особый элемент, он имеет ту же структуру, что и другие элементы. Поэтому инструкция INSQUE может использоваться для вставки элемента после любого заданного элемента при условии, что доступен адрес данного элемента. Например, в структуре HEAD, C, A, D, B адрес HEAD+4 содержит адрес B. Как описано в гл. 7, операнд @HEAD+4 определяет адрес, содержащийся в HEAD+4. В результате этого инструкция

INSQUE  E,@HEAD+4

рассматривает элемент B как головной элемент списка и вставляет элемент E между элементами B и HEAD, порождая структуру HEAD, C, A, D, B, E.

Аналогичные методы адресации могут применяться и с инструкцией REMQUE. Например;

REMQUE  @HEAD,R0    ;УДАЛИТЬ ЭЛЕМЕНТ СЛЕДУЮЩИЙ ЗА HEAD
REMQUE  @HEAD+4,R0  ;УДАЛИТЬ ЭЛЕМЕНТ ПРЕДШЕСТВУЮЩИЙ HEAD

Очередь - это такая структура данных, в которой первый добавленный элемент первым же и удаляется (используется стратегия FIFO - "первым вошёл первым вышел"). Очереди легко реализуются с помощью инструкций INSQUE item,HEAD и REMQUE HEAD+4,dist, которые вставляют элементы после головного элемента и удаляют элементы, предшествующие головному элементу. Очередь противоположна стеку, в котором используется другая организация (LIFO - "последний вошёл, первым вышел"). Однако реализация очередей в ЭВМ VAX даёт более мощную структуру данных, чем обычная очередь (или стек), поскольку она основана па двунаправленном списке. В результате этого включённые в структуру элементы могут быть произвольно расположены в памяти, данные, связанные с каждым элементом, могут иметь любую длину и легко могут быть изменены, а элементы могут легко вставляться или удаляться из любого места структуры данных.

Кроме абсолютных очередей ЭВМ семейства VAX поддерживают относительные очереди, в которых указатели являются относительными, а не абсолютными адресами. Имеются инструкции для вставки или удаления элементов, предшествующих или следующих за головным элементом очереди. Дополнительные подробности см. в книге "VAX Architecture Handbook".

УПРАЖНЕНИЯ 13.2

  1. Используя формулу Ньютона-Рафсона, описанную в гл. 12, вычислите корень квадратный из числа 2 с точностью не меньше 25 десятичных знаков. Однако вычисления должны выполняться, с использованием упакованных десятичных чисел, а не чисел с плавающей точкой. Используйте инструкцию CVTPS для распечатки последовательных приближений.
  2. В разд. 13.7 приведён фрагмент программы, в котором число со знаком в формате квадраслова преобразуется в 20-значное упакованное десятичное число. Покажите, как этот алгоритм работает с каждым из следующих квадраслов:

    а).

    ^X0000001000000001

    б).

    ^X0000000080000000

    в).

    ^X8000000000000000

    г).

    ^X8000000080000000

    д).

    ^X0000000180000001

    е).

    ^X8000000180000001

    ж).

    ^XC0000000C0000000

    з).

    ^XC0000001C0000001

  3. Напишите подпрограмму, которая преобразует целое число со знаком в формате длинного слова в числовую строку с раздельным ведущим знаком. Строка должна иметь длину 10, а это означает, что она занимает 11 байтов. (Подсказка: Используйте инструкции CVTLP и CVTPS.)
  4. Модифицируйте подпрограмму из п. 3 так, чтобы подавлялись ведущие нули, а знак перемещался вправо таким образом, чтобы он оказывался перед первой значащей цифрой (положительный нуль должен распечатываться так: ________+0).
  5. Напишите подпрограмму, которая распечатывает число F_FLOAT по формату F10.0. Фортрана (число в формате F10.0 состоит из знака, шести десятичных цифр, десятичной точки и двух цифр после десятичной точки). Однако вместо знака "плюс" используется пробел, ведущие нули подавляются, а знак переносится к первой значащей цифре. (Подсказка: умножьте число F_FLOAT на 100 и затем используйте инструкции CVTFL, CVTLP и EDITPC.)
  6. Напишите программу, которая считывает числа, представленные как символьные строки длиной 20, и преобразует число в формат G с плавающей точкой. Символьная строка начинается со знака "плюс" или "минус" и содержит десятичную точку, вставленную где-то между 18 десятичными цифрами (например, +00000000012345.6789). (Подсказка: удалите десятичную точку и разбейте символьную строку на две 9-значные числовые строки с раздельным ведущим знаком. Преобразуйте в два упакованных десятичных числа с помощью инструкции CVTSP, а затем преобразуйте в два целых числа в формате длинного слова с помощью инструкции CVTPL. Используйте инструкцию CVTLG, применив подходящее масштабирование, для преобразования длинных слов в формат G с плавающей точкой.)
  7. Как в п. 3 из упр. 13.1, напишите программу, которая составляет алфавитный указатель слов заданного текста. Однако в вашей программе должна использоваться такая структура данных, в которой каждое слово текста представляется элементом очереди.

 

< НАЗАД ОГЛАВЛЕНИЕ ВПЕРЁД >


[1] В ЭВМ серии IBM 360 и последующих моделях этого ряда инструкцией, аналогичной инструкции MOVC3, является MVC (переслать символы). Она реализована так, что осуществляет пересылку по одному символу и используется для распространения байта по строке.