Деление. div и idiv

Последнее обновление: 19.07.2023

Для деления чисел в архитектуре 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 не влияют на флаги переноса, переполнения, знака и нуля.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850