Мне нужно получить разницу в 2 знаковых целых числах. Есть ли функция ABS() на языке ассемблера x86, чтобы я мог это сделать. Любая помощь будет принята с благодарностью.
X86 assembly abs() реализация?
Ответ 1
Если это сборка x86, следующая работа в соответствии с полезной wikipedia. Вычтите одно значение из другого, а затем используйте следующие инструкции для результата:
cdq
xor eax, edx
sub eax, edx
Ответ 2
Так как функция библиотеки C abs()
выполняет ее в сборке без разветвления:
abs(x) = (x XOR y) - y
где y = x >>> 31
(при условии 32-битного ввода), а >>>
- арифметический оператор сдвига вправо.
Объяснение приведенной выше формулы:
Мы хотим генерировать только 2 дополнения отрицательного x
.
y = 0xFFFF, if x is negative
0x0000, if x is positive
Итак, когда x
положительно x XOR 0x0000
равно x
. А когда x
отрицательный x XOR 0xFFFF
равен 1 дополнению x
. Теперь нам просто нужно добавить 1
, чтобы получить его 2 дополнение, что и делает выражение -y
. Потому что 0xFFFF
равно -1 в десятичной системе.
Посмотрите на сборку, сгенерированную для следующего кода, на gcc
(4.6.3 на моей машине):
C-код:
main()
{
int x;
int output = abs(x);
}
gcc 4.6.3 сгенерированный фрагмент сборки (синтаксис AT & T) с моими комментариями:
movl -8(%rbp), %eax # -8(%rbp) is memory for x on stack
sarl $31, %eax # shift arithmetic right: x >>> 31, eax now represents y
movl %eax, %edx #
xorl -8(%rbp), %edx # %edx = x XOR y
movl %edx, -4(%rbp) # -4(%rbp) is memory for output on stack
subl %eax, -4(%rbp) # (x XOR y) - y
БОНУС (из Hacker Delight): Если у вас есть быстрое умножение на +1 и -1, следующий даст вам abs(x)
:
((x >>> 30) | 1) * x
Ответ 3
Если вы хотите правильно обрабатывать все случаи, вы не можете просто вычесть, а затем принять абсолютное значение. Вы столкнулись с проблемой, потому что различие двух значащих целых чисел не обязательно представляется в виде целого числа со знаком. Например, предположим, что вы используете 32-битные целые числа дополнений, и вы хотите найти разницу между INT_MAX
(0x7fffffff
) и INT_MIN
(0x80000000
). Вычитание дает:
0x7fffffff - 0x80000000 = 0xffffffff
который равен -1
; когда вы принимаете абсолютное значение, результат получается 1
, тогда как фактическая разница между двумя числами 0xffffffff
интерпретируется как целое без знака (UINT_MAX
).
Разница между двумя целыми знаками всегда представляется как целое число без знака. Чтобы получить это значение (с аппаратным обеспечением с дополнением 2s), вы просто вычитаете меньший вход из большего и интерпретируете результат как целое число без знака; нет необходимости в абсолютном значении.
Здесь один (из многих, а не обязательно лучший) способ сделать это на x86, предполагая, что два целых числа находятся в eax
и edx
:
cmp eax, edx // compare the two numbers
jge 1f
xchg eax, edx // if eax < edx, swap them so the bigger number is in eax
1: sub eax, edx // subtract to get the difference
Ответ 4
Старая нить, но если я зашел сюда поздно, возможно, тоже... abs - блестящий пример, так что это должно быть здесь.
; abs(eax), with no branches.
; intel syntax (dest, src)
mov ebx, eax ;store eax in ebx
neg eax
cmovl eax, ebx ;if eax is now negative, restore its saved value
Ответ 5
Предполагая, что ваши целые числа находятся в регистрах MMX или XMM, используйте psubd
для вычисления разницы, а затем pabsd
, чтобы получить абсолютное значение разницы.
Если ваши целые числа находятся в равных, "нормальных" регистрах, затем выполните вычитание, затем трюк cdq
, чтобы получить абсолютное значение. Для этого необходимо использовать некоторые конкретные регистры (cdq
sign-extends eax
в edx
, не используя никакой другой регистр), поэтому вы можете делать что-то с другими кодами операций. Например:.
mov r2, r1
sar r2, 31
вычисляет в регистре r2
знак-расширение r1
(0, если r1
положительный или нулевой, 0xFFFFFFFF, если r1
отрицательный). Это работает для всех 32-разрядных регистров r1
и r2
и заменяет инструкцию cdq
.
Ответ 6
Короткий, но простой способ, используя условную инструкцию перемещения (имеется в наличии Pentium и выше):
; compute ABS(r1-r2) in eax, overwrites r2
mov eax, r1
sub eax, r2
sub r2, r1
cmovg eax, r2
Подпрограмма устанавливает флаги так же, как и инструкция cmp.
Ответ 7
АБС (EAX)
test eax, eax ; Triger EFLAGS [CF, OF, PF, SF, and ZF]
jns AbsResult ; If (SF) is off, jmp AbsResult
neg eax ; If (SF) is on. (negation nullify by this opcode)
AbsResult:
Если флаги уже установлены с помощью любого генерируемого значения в eax, вам не нужно test
. Неправильные предсказания отрасли сделают это медленным, если входные значения будут случайным образом распределены между положительными и отрицательными.
Это работает аналогично для RAX, AX, AL.
Ответ 8
Существует инструкция SUB, если вы хотите сделать A-B. НТН