Для деления чисел в архитектуре x86-64 предназначены инструкции div и idiv. idiv делит два числа со знаком, а div - беззнаковые числа. Эти инструкции принимают следующие формы:
div reg8 div reg16 div reg32 div reg64 div mem8 div mem16 div mem32 div mem64 idiv reg8 idiv reg16 idiv reg32 idiv reg64 idiv mem8 idiv mem16 idiv mem32 idiv mem64
Инструкции принимают в качестве единственного операнда 8-, 16-, 32-, 64-разрядные регистры или 8-, 16-, 32-, 64-разрядные переменные.
Инструкция div делит два беззнаковых числа. Если операнд 8-разрядный, то div делит регистр AX на операнд, помещая частное в AL, а остаток (по модулю) в AH.
Если операнд 16-разрядный, то инструкция div делит 32-разрядное число в DX:AX на операнд, помещая частное в AX, а остаток в DX.
Если операнд 32-разрядный, div делит 64-битное число в EDX:EAX на операнд, помещая частное в EAX, а остаток в EDX.
И если операнд 64-разрядный, div делит 128-битное число в RDX:RAX на операнд, помещая частное в RAX, а остаток в RDX.
Инструкция idiv имеет аналогичное действие, только в качестве операндов принимает числа со знаком.
Пример деления:
.code main proc xor rax, rax mov ax, 22 ; 16-разрядный регистр mov bl, 5 ; 8-разрядный регистр div bl ; AL =4 (результат), AH = 2 (остаток) movzx eax, al ret main endp end
Здесь делим 16-разрядное значение (регистр AX) на 8-разрядное значение (регистр BL). После деления в AL помещается результат (в данном случае число 4), а в AH - остаток (здесь число 2).
Чуть более сложный случай - надо разделить 32-рарядное число на 16-разрядное.
.code main proc mov eax, 24003h ; 32-разрядное число mov edx, eax ; копируем данные из EAX и EDX shr edx, 16 ; сдвигаем биты вправо, чтобы старшие 16 бит из EAX оказались в DX mov bx, 10h ; 16-разрядный регистр div bx ; AX =1200h (результат), DX = 3 (остаток) movzx eax, ax ; EAX = 1200h = 9216 ret main endp end
Здесь мы имеем 32-разрядное число в EAX. Чтобы его разделить на 16-разрядлное число из BX, копируем EAX в EDX и затем сдвигаем скопированные биты на 16 разрядов вправо, чтобы старшие 16 бит из EAX оказались в DX.
При этом в x86-64 нельзя просто разделить два числа одинаковой разрядности, например, одно 8-разрядное на другое 8-разрядное. Если знаменатель представляет собой 8-битное значение, числитель должен быть 16-битным значением. Если же нужно разделить одно 8-битное значение без знака на другое, то необходимо дополнить числитель нулями до 16 бит, загрузив числитель в регистр AL, а затем переместив 0 в регистр AH. Отсутствие расширения AL до нуля перед выполнением div может привести к тому, что x86-64 выдаст некорректный результат.
Если нужно разделить два беззнаковых 16-разрядных числа, то надо расширить регистр AX (который содержит числитель) до регистра DX. Для этого достаточно загрузить 0 в регистр DX.
Если нужно разделить одно беззнаковое 32-битное значение на другое, перед делением надо расширить регистр EAX нулями до EDX (загрузив 0 в EDX).
И чтобы разделить одно 64-битное число на другое, перед делением нужно расширить RAX нулями до RDX (поместив 0 в RDX).
Перед делением целочисленных значений со знаком одинаковой разрядности с помощью idiv необходимо расширить знаком AL в AX (поместив знаковый бит числа в AH),
AX в DX (знаковый бит в DX), EAX в EDX (знаковый бит в EDX) или RAX в RDX (знаковый бит в RDX). Для этого можно использовать следующие инструкции cbw, cwd, cdq
или
cqo
cbw: преобразует байт в AL в слово в AX через расширение знаком
cwd: преобразует слово в AX в двойное слово в DX:AX через расширение знаком
cdq: преобразует двойное слово в EAX в четверное слово в EDX:EAX с помощью расширения знаком
cqo: преобразует четверное слово в RAX в восьмеричное слово в RDX:RAX через расширение знаком
cwde: преобразует слово в AX в двойное слово в EAX с помощью расширения знаком
cdqe: преобразует двойное слово в EAX в четверное слово в RAX с помощью расширения знаком
Эти инструкции не принимают операндов. Отсутствие расширения знаком может привести к неверным результатам. Например, разделим два 16-разрядных числа со знаком:
.code main proc ; два 16-разрядные числа mov ax, -20 mov bx, 10 ; заполним DX знаковым битом из AX cwd idiv bx ; AX =-2 (результат), DX = 0 (остаток) movsx eax, ax ; EAX = -2 ret main endp end
При делении стоит учитывать еще ряд моментов. Прежде всего, как и в целом в стандартной математике, на ноль делить нельзя. При попытке деления на ноль x86-64 сгенерирует исключение деления.
Другая проблема заключается в том, что частное может быть слишком большим, чтобы поместиться в регистр RAX, EAX, AX или AL. Например, 16/8-битное деление 8000h/2 дает частное 4000h с остатком 0. 4000h не помещается в 8 бит. Если это произойдет, x86-64 сгенерирует исключение исключение целочисленного переполнения. Поэтому имеет смысл проверять значения перед делением.
Также стоит отметить, что инструкции div
и idiv
не влияют на флаги переноса, переполнения, знака и нуля.