Для деления чисел в ассемблере мы можем использовать инструкции div и idiv. Причем эти операции позволяют разделить максимум 128-битное значение, которое помещается в RDX:RAX, а делителем в этом случае является 64-битный операнд. После деления частное помещается в RAX, а остаток - в RDX. Однако в этом случае результат деления должен умещаться в 64 бита, что естественно происходит не всегда (например, любое число больше 64 бит разделить на 1).
Один из методов для решения этой задачи состоит в делении старших 32 битов числа (дополненного нулем или знаком) на делитель, затем делится остаток и младшие 32 бита:
.data op1 oword 100000000000000006h ; делимое op2 qword 2 ; делитель result qword 2 dup (?) remainder qword ? ; для остатка .code main proc mov rax, qword ptr op1[8] xor edx, edx ; дополнение нулем для беззнакового деления div op2 mov result[8], rax ; сохраняем в старшие 64 байта mov rax, qword ptr op1[0] div op2 ; делим остаток и младшие 64 бита mov result[0], rax ; сохраняем в младшие 64 байта mov remainder, rdx ; сохраняем остаток ret main endp end
Переменная частного имеет размер 128 бит, потому что для результата может потребоваться столько же битов, сколько и для делимого (например, при делении на 1). Независимо от размера операндов делимого и делителя, остаток никогда не превышает 64 бита (в данном случае).
Для деления 128-разрядного числа на 64-разрядное, сначала делим старшие 64 бит делимого на делитель. Частное из этого первого деления становится старшими 32 битами конечного результата. Остаток от этого деления остается в RDX для второй операции деления.
Вторая операция деления делит rdx:op1[0]
на делитель для получения младших 32 бит результата и остатка от деления.
Данная операция деления (128 на 64) является частным случаем общего алгоритма деления значений произвольного размера на 64-битный делитель. Общий алгоритм следующий:
64 старших бита делимого помещаются в RAX с расширением нулями в RDX.
Деление этих 64 бит на делитель.
Результат деления из регистра RAX сохраняется в соответствующей позиции переменной результата
В RAX загружаются следующие 64 бита без изменения RDX (там должен быть остаток).
Повторение шагов 2-4 пока не будут обработаны все 64-битные части делимого
Пример деления 256-разрядного числа на 64-разрядное:
.data op1 oword 2222eeeeccccaaaa8888666644440000h oword 2222eeeeccccaaaa8888666644440000h ; делимое - 256 бит op2 qword 2 ; делитель result qword 2 dup (?) remainder qword ? ; для остатка .code main proc mov rax, qword ptr op1[24] ; старшие 64 бита в RAX xor rdx, rdx ; расширение нулями для беззнакового деления div op2 ; делим старшие 64 бита mov result[24], rax ; сохраняем старшие 64 бита результата mov rax, qword ptr op1[16] ; следующие 64 бита div op2 mov result[16], rax ; сохраняем вторую порцию 64 бит mov rax, qword ptr op1[8] div op2 mov result[8], rax mov rax, qword ptr op1[0] ; младшие 64 бита div op2 mov result[0], rax ; сохраняем младшие 64 бита mov remainder, rdx ; сохраняем остаток ret main endp end
Стоит отметить, что данный алгоритм деления работает только для беззнаковых операндов. Чтобы разделить два числа со знаком, надо зафиксировать их знаки, взять их абсолютные значения, выполнить беззнаковое деление, а затем установить знак результата на основе знаков операндов.
При мере выше мы могли реализовать делимое произвольного размера, но делитель оставался 64-разрядным. Для реализации делителей размером более 64 бит одним из вариантов решения проблемы является сочетание сдвига и вычитания. В общем случае алгоритм будет выглядеть следующим образом:
Quotient = Dividend; Remainder = 0; for i = 1 to NumberBits do Remainder:Quotient = Remainder:Quotient SHL 1; if Remainder >= Divisor then Remainder = Remainder - Divisor; Quotient = Quotient + 1; endif endfor
Где Quotient - результат/частное, Dividend - делимое, Remainder - остаток от деления, Divisor - делитель, а NumberBits - количество битов в Remainder/Quotient/Divisor/Dividend. Здесь делимое и результат деления представляют один и тот же объект. Суть алгоритма - сдвигаем Remainder:Quotient на один бит влево. Таким образом в остатке будет первый бит делимого. Если получение значение в остатке больше или равно делителю, то вычитаем из остатка делитель и увеличиваем результат на 1. И так, пока не обработаем все биты в делители.
Реализация на примере деления 128-разрядного числа на другое 128-разрядное:
.data op1 oword 00000000000000040000000000000008h ; делимое и результат - 128 бит op2 oword 2 ; делитель - 128 бит remainder oword ? .code main proc mov cl, 128 ; в CL количество бит, которые надо разделить ; Remainder:Quotient = Remainder:Quotient SHL 1 repeatLp: ; сдвиг влево на 1 разряд всего делимого и остатка shl qword ptr op1[0], 1 rcl qword ptr op1[8], 1 rcl qword ptr remainder[0], 1 rcl qword ptr remainder[8], 1 ; Выполняем 128-разрядное сравнение, чтобы узнать, больше ли остаток чем делитель mov rax, qword ptr remainder[8] cmp rax, qword ptr op2[8] ja isGE ; если больше или равно jb notGE ; если меньше mov rax, qword ptr remainder cmp rax, qword ptr op2 ja isGE jb notGE ; Remainder = Remainder - Divisor isGE: mov rax, qword ptr op2 sub qword ptr remainder, rax mov rax, qword ptr op2[8] sbb qword ptr remainder[8], rax ; Quotient = Quotient + 1 add qword ptr op1, 1 adc qword ptr op1[8], 0 notGE: dec cl jnz repeatLp ; продолжаем пока не пройдем по всем 128 битам ; mov eax, dword ptr op1 ; 4 ; mov eax, dword ptr op1[8] ; 2 ret main endp end
В данном случае переменная op1 выступает в качестве делимого и одновременно результата операции. Переменная op2 является делителем, а remainder - хранит остаток. В регистре CL хранится счетчик битов, которые надо разделить.