Арифметические сдвиги

Арифметический сдвиг влево эквивалентен логическому сдвигу влево (это одна и та же команда) – «освобождающие» биты заполняются нулями. При арифметическом сдвиге вправо «освобождающиеся» биты заполняются знаковым битом. Последний ушедший бит сохраняется во флаге CF.

SAL <операнд>, <количество>; Арифметический сдвиг влево

SAR <операнд>, <количество>; Арифметический сдвиг вправо

Циклические сдвиги

При циклическом сдвиге «освобождающиеся» биты заполняются ушедшими битами. Последний ушедший бит сохраняется во флаге CF.

ROL <операнд>, <количество>; Циклический сдвиг влево

ROR <операнд>, <количество>; Циклический сдвиг вправо

Расширенные сдвиги

Расширенные сдвиги немного отличаются от остальных сдвигов. В расширенных сдвигах участвуют два регистра или ячейка памяти и регистр, которые как бы объединяются в единое целое и «освобождающиеся» биты одного операнда заполняются битами из другого операнда.

SHLD <операнд1>, <операнд2>, <количество>; Расширенный сдвиг влево

SHRD <операнд1>, <операнд2>, <количество>; Расширенный сдвиг вправо

Команда SHLD сдвигает влево биты операнда1 на указанное количество позиций. Младшие («освободившиеся») биты операнда1 заполняются старшими битами операнда2. Сам операнд2 не меняется.

Команда SHRD сдвигает вправо биты операнда1 на указанное количество позиций. Старшие («освободившиеся») биты операнда1 заполняются младшими битами операнда2. Сам операнд2 не меняется.

Количество, как и в других операциях сдвига, задаётся непосредственным операндом или хранится в регистре CL. Но используются только последние 5 бит операнда, определяющего количество, т.е. максимальное количество позиций сдвига равно 32.

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

Умножение и деление с помощью поразрядных операций

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

Умножение

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

mov ax, 250; AX = 00fah = 250

sal ax, 4; Умножение на 24 = 16, AX = 0fa0h = 4000

mov ax, 1; AX = 1

sal ax, 10; Умножение на 210, AX = 0400h = 1024

mov ax, -48; AX = ffd0h = -48 (в дополнительном коде)

sal ax, 2; AX = ff40h = -192 (в дополнительном коде)

mov ax, 26812; AX = 68bch = 26812

sal ax, 1; AX = d178h = -11912

; Знаковое положительное число перешло в отрицательное

mov ax, 32943; AX = 80afh = 32943

sal ax, 2; AX = 02bch = 700

; Большое беззнаковое число стало гораздо меньше

Сочетая сдвиги со сложением и вычитанием можно выполнить умножение на любое положительное число. Для умножения на отрицательное число следует добавить команду NEG.

mov ebx, x

mov eax, ebx

sal eax, 2

add eax, ebx; EAX = x * 5

mov ebx, x

mov eax, ebx

sal eax, 3

sub eax, ebx; EAX = x * 7

mov ebx, x

mov eax, ebx

sal eax, 2

add eax, ebx

sal eax, 1; EAX = x * 10

Такой набор операций выполняется в 1.5-2 раза быстрее, чем обычное умножение. Но если оба сомножителя заранее неизвестны, то лучше использовать умножение.

Деление

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

Для деления беззнаковых чисел следует использовать логический сдвиг вправо.

mov ax, 43013; AX = a805h = 43013

shr ax, 1; AX = 5402h = 21506

Со знаковыми числами дело обстоит несколько сложнее. В принципе, для деления знаковых чисел следует использовать арифметический сдвиг вправо. Однако для отрицательных чисел получается не совсем корректный результат: 1 / 2 = 0, 3 / 2 = 1, но -1 / 2 = -1, -3 / 2 = -2,, т.е. результат отличается от правильного на единицу. Для того чтобы получить правильный результат, необходимо прибавить к делимому делитель, уменьшенный на 1. Однако это необходимо только для отрицательных чисел, поэтому для того, чтобы не делать проверок, используют следующий алгоритм.

; Деление на 2

mov eax, x

cdq; Расширяем двойное слово до учетверённого. Если в регистре EAX находится положительное число,

; то регистр EDX будет содержать 0, а если в регистре EAX находится отрицательное число,

; то регистр EDX будет содержать -1 (ffffffffh)

sub eax, edx; Если регистр EDX содержит 0, то регистр EAX не меняется. Если же регистр EDX содержит -1

; (при отрицательном EAX), то к EAX будет прибавлена требуемая единица

sar eax, 1

; Деление на 2 n (в данном примере n = 3)

mov eax, x

cdq; Расширяем двойное слово до учетверённого

and edx, 111b; Если EAX отрицателен, то EDX содержит делитель, уменьшенный на 1

add eax, edx; Если EAX отрицателен, прибавляем полученное значение

sar eax, 3; Если EAX был положителен, то EDX = 0, и предыдущие две операции ничего не меняют

Если число беззнаковое или если мы знаем, что число положительное, можно просто использовать сдвиг вправо, который выполняется примерно в 10 раз быстрее, чем деление. Если же для знакового числа не известно, положительное оно или отрицательное, то придётся использовать вышеприведённую последовательность команд, которая, однако, также выполняется примерно в 5-7 раз быстрее, чем деление.


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: