2010-04-14 2 views
16

Мне нужно получить разницу в 2 значащих целых числа. Есть ли функция ABS() на языке ассемблера x86, чтобы я мог это сделать. Любая помощь будет принята с благодарностью.x86 assembly abs() реализация?

+0

Вы можете сравнить и условно обменять, а затем вычесть. –

+0

На какой платформе вы работаете? Нет такой вещи, как «Язык ассемблера», только «сборка x86» или «сборка ARM» и т. Д. –

+0

Является ли сборка x86 .. –

ответ

15

Если это сборка x86, то должны работать следующие according to the ever useful wikipedia. Вычесть одно значение из другого, а затем использовать эти инструкции по результату:

cdq 
xor eax, edx 
sub eax, edx 
14

Если вы хотите, чтобы обрабатывать все случаи правильно, вы не можете просто вычитать, а затем взять абсолютное значение. Вы столкнулись с проблемой, потому что различие двух значащих целых чисел не обязательно представляется в виде целого числа со знаком. Например, предположим, что вы используете 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 
+1

Использование 'jge' может привести к« предсказанию ветвления »в процессоре« неправильное предсказание », что резко снизит процессор. Итак, если производительность вызывает беспокойство, лучше использовать ответ от @bits или @Hal –

0

Существует команда SUB, если то, что вы хотите это сделать AB. НТН

4

Предполагая, что ваши числа в 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.

4

короткий, но простой способ, с помощью инструкции условного перемещения (доступно Pentium и выше я думаю):

; compute ABS(r1-r2) in eax, overwrites r2 
mov eax, r1 
sub eax, r2 
sub r2, r1 
cmovg eax, r2 

Инструкция к югу устанавливает флаги такие же, как в инструкции КСС.

+0

. Cmov был новым с P6 (ppro/PII), но да, вы можете предположить это в наши дни. gcc делает. –

8

Старая ветка, но если бы я зашел сюда поздно, возможно, тоже ... 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 
+1

Это действительно просто и эффективно, избегая «прогнозирования ветвей», определенно должно быть принято как ответ. –

16

Это, как функция 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's Delight): Если у вас есть быстрый умножить на +1 и -1, следующий даст вам abs(x):

 ((x >>> 30) | 1) * x 
+0

Дополнительные скобки в формуле BONUS – socketpair

+0

спасибо! обновлено :) – bits

1

ABS (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.

+0

'или reg, reg' всегда хуже, чем' test reg, reg'. Http: // StackOverflow.ком/вопросы/33721204/x86 сборка-ОГТ-р-0-против-или-р-р/33724806 # 33724806. Кроме того, ветви не являются «одним часом». Они либо равны нулю (предсказано правильно), либо ~ 15 часов (неверно предсказано). –