6.3. Умножение и деление с помощью поразрядных операций
Для любой системы счисления сдвиг числа влево или вправо соответствует умножению или делению на основание системы счисления в некоторой степени. Двоичная система счисления, используемая в компьютере, не является исключением. Причём команды сдвига работают на порядок быстрее обычных операций умножения и деления.
6.3.1. Умножение
Для умножения используется сдвиг влево. Несмотря на наличие двух команда, по сути, сдвиг влево один. Он используется для умножения как знаковых, так и беззнаковых чисел. Однако результат будет правильным, только в том случае, если он умещается в регистр или ячейку памяти.
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 раза быстрее, чем обычное умножение. Но если оба сомножителя заранее неизвестны, то лучше использовать умножение.
6.3.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 ; Деление на 2n (в данном примере 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 раз быстрее, чем деление.
Do'stlaringiz bilan baham: |