Реализация операций сдвига в отношении больших чисел подразумевает сочетание операций сдвига и вращения. Для реализации сдвига влево можно применить инструкции shl и rcl
Например, чтобы выполнить сдвиг влево 128-разрядного числа, сначала необходимо сдвинуть младшие 64 разрядов влево (с помощью инструкции shl). При этом надо также получить значение самого старшего бита из этих младших 64 разрядов, который отбрасывается при сдвиге влево. Для этого применяется обычно флаг переноса CF. Затем надо сдвинуть этот бит в самый младший бит старщих 64 разрядов числа, одновременно сдвигая все остальные биты влево. Для этого применяется инструкция rcl.
Рассмотрим на примере сдвига влево 192-разрядного числа:
.data ; 192-разрядное число num qword 0ffffffffffffffffh, 0e000000000000000h, 0 .code main proc shl qword ptr num, 1 ; сдвигаем младшие 64 бита влево rcl qword ptr num[8], 1 ; средние 64 бита rcl qword ptr num[16], 1 ; старшие 64 бита ret main endp end
Чтобы учесть сдвигаемый старший бит из предыдущих 64 разрядов для сдвига вторых и последующих 64 разрядов числа применяется инструкция вращения влево с переносом rcl. Однако поскольку нам надо учитывать сдвигаемый старший бит из предыдущих 64 разрядов, то одномоментно мы можем сдвинуть число только на 1 бит. Что может быть не очень удобно, особенно если нам надо сдвинуть на большее количество разрядов. В этом случае можно использовать циклические конструкции:
.data ; 192-разрядное число num qword 0ffffffffffffffffh, 0e000000000000000h, 0 .code main proc mov cl, 4 ; сдвигаем на 4 разряда shift: shl qword ptr num, 1 ; сдвигаем младшие 64 бита влево rcl qword ptr num[8], 1 ; средние 64 бита rcl qword ptr num[16], 1 ; старшие 64 бита dec cl jnz shift ; пока не сдвинем все разряды, повторяем цикл mov rax, qword ptr num[16] ; RAX = 0eh или 14 ret main endp end
Аналогичным образом можно использовать сдвиг вправо (инструкция shr) и арифметический сдвиг вправо (инструкция sar), только сдвиг начинается со старших 64 разрядов числа:
; обычный сдвиг вправо sar qword ptr num[16], 1 rcr qword ptr num[8], 1 rcr qword ptr num[0], 1 ; арифметический сдвиг вправо shr qword ptr num[16], 1 rcr qword ptr num[8], 1 rcr qword ptr num[0], 1
Для сдвига на несколько бит мы можем использовать циклическую конструкцию, как было показано выше. Однако ассемблер также предоставляет и другой способ - инструкции shld (сдвиг влево) и shrd (сдвиг вправо). Они позволяют сдвинуть число на несколько бит. Они имеют следующий синтаксис:
shld Operand1, Operand2, constant shld Operand1, Operand2, cl shrd Operand1, Operand2, constant shrd Operand1, Operand2, cl
В качестве первого операнда регистр или переменная, которые могут быть 16-, 32- или 64-разрядными. В первый операнд помещается результат сдвига. Второй операнд - собственно сдвигаемое число, в качестве которого должен выступать регистр. Оба операнда должен иметь одинаковый размер. Третий операнд - константа или регистр CL указывают, на сколько битов надо выполнить сдвиг.
При сдвигах с помощью данных иснтрукций также могут меняться знаки. При сдвиге влево инструкцией shld флаг переноса CF содержит последний сдвинутый старший бит из первого операнда. Если сдвигаем только на 1 бит, флаг переполнения OF будет содержать 1, если при сдвиге изменится знак первого операнда. Если сдвигаем на большее число битов, флаг переполнения не определен. Флаг будет ZF равен 1, если результатом сдвига является 0. Флаг знака SF будет содержать старший бит результата.
При сдвиге вправо инструкцией shrd флаг переноса CF содержит последний сдвинутый младший бит из первого операнда. Если сдвигаем только на 1 бит, флаг переполнения OF будет содержать 1, если при сдвиге изменится старший бит первого операнда. Если сдвигаем на большее число битов, флаг переполнения не определен. Флаг будет ZF равен 1, если результатом сдвига является 0. Флаг знака SF будет содержать старший бит результата.
Например, перепишем предыдущий пример со сдвигом 192-разрядного числа на 4 бита с помощью инструкции shld:
.data ; 192-разрядное число num qword 0ffffffffffffffffh, ; младшие 64 бита 0e000000000000000h, ; средние 64 бита 0 ; старшие 64 бита .code main proc mov rax, num[8] shld num[16], rax, 4 mov rax, num[0] shld num[8], rax, 4 shl num[0], 4 ; mov rax, qword ptr num[16] ; RAX = 14 ; mov rax, qword ptr num[8] ; RAX = 15 ret main endp end
Первая инструкция shld
сдвигает биты из num[8]
в num[16]
, не затрагивая значения в num[8]
.
Вторая инструкция shld
сдвигает биты из num
в num[8]
. Наконец, инструкция shl
сдвигает младшие 64 бита из num
на 4 разряда влево.
Аналогичным образом сдвиг вправо с помощью shrd
.