Используйте cdq
/ idiv
для знакового 32-битного / 32-битного => 32-битного деления,
xor edx,edx
/ div
для беззнакового.
С делимым в EAX для начала и делителем, указанным в качестве операнда для DIV или IDIV.
mov eax, 1234
mov ecx, 17
cdq ; EDX = signbit(EAX)
idiv ecx ; EAX = 1234/17 EDX = 1234%17
Если вы обнулите EDX/RDX вместо расширения знака в EDX:EAX до idiv
, можно получить большой положительный результат для -5/2, например.
Использование «полной мощности» 64/32-битного => 32-битного деления возможно, но небезопасно, если вы не знаете, что делитель достаточно велик, чтобы частное не переполнялось. (т. е. вы не можете вообще реализовать (a*b) / c
только с mul
/ div
и 64-битным временным файлом в EDX:EAX.)
Разделение вызывает исключение (#DE) при переполнении частного. В Unix/Linux ядро поставляет SIGFPE для арифметических исключений, включая ошибки деления. При нормальном знаке или делении с нулевым расширением переполнение возможно только fpe/46378352">с idiv
из INT_MIN / -1
(т. е. 2-й дополнительный частный случай самого отрицательного числа.)
Как видно из справочного руководства insn (ссылка в разделе x86 тег вики):
- один операнд
mul
/imul
: edx:eax = eax * src
- два операнда
imul
: dst *= src
. например imul ecx, esi
не читает и не записывает eax или edx.
div
/ idiv
: делит edx:eax
на источник. частное в eax
, остаток в edx
. Не существует формы div
/idiv
, которая игнорирует edx
во входных данных.
cdq
sign расширяет eax
до edx:eax
, т. е. передает бит знака eax
в каждый бит edx
. Не путать с cdqe
, 64-битной инструкцией, которая является более компактной формой movsxd rax, eax
.
Первоначально (8086) было только cbw
(ax = sign_extend(al)
) и cwd
(dx:ax = sign_extend(ax)
). Расширения x86 до 32-битных и 64-битных сделали мнемонику немного двусмысленной (но помните, кроме cbw
, версии внутри eax всегда заканчиваются на e
для расширения). Инструкции dl=sign_bit(al) нет, потому что 8-битные mul и div являются специальными и используют ax
вместо dl:al
.
Поскольку входные данные для [i]mul
являются одиночными регистрами, вам не нужно ничего делать с edx
перед умножением.
Если ваш ввод подписан, вы расширяете его, чтобы заполнить регистр, который вы используете в качестве ввода для умножения, например. с movsx
или cwde
(eax = sign_extend(ax)
). Если ваш ввод не подписан, вы нулевое расширение. (За исключением того, что если вам нужны только младшие 16 бит результата умножения, например, не имеет значения, содержат ли старшие 16 бит одного или обоих входных данных мусор.)
Для деления вам всегда нужно обнулить или расширить знак eax в edx. Обнуление — это то же самое, что и безусловное обнуление edx, поэтому для этого нет специальной инструкции. Просто xor edx,edx
.
cdq
существует потому, что он намного короче, чем mov edx, eax
/ sar edx, 31
, чтобы передать бит знака eax каждому биту в edx. Кроме того, сдвиги с немедленным счетчиком > 1 не существовали до 186 и по-прежнему составляли 1 цикл на счет, поэтому на 8086 вам пришлось бы делать что-то еще хуже (например, перейти или повернуть бит знака вниз и изолировать + neg
Это). Таким образом, cwd
в 8086 сэкономил много времени/пространства, когда это было необходимо.
В 64-битном режиме знак и ноль расширяют 32-битные значения до 64-битных. ABI допускает мусор в старших 32 битах 64-битного регистра, содержащего 32-битное значение, поэтому, если ваша функция должна просматривать только младшие 32 бита edi
, вы не можете просто использовать [array + rdi]
для индексации массива.
Таким образом, вы видите много movsx rdi, edi
(расширение знака) или mov eax, edi
(расширение нуля, и да, более эффективно использовать другой целевой регистр, потому что Intel mov-elimination не работает с mov same,same
)
person
Peter Cordes
schedule
07.04.2016