x86 сборка abs () реализация? - PullRequest
19 голосов
/ 14 апреля 2010

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

Ответы [ 8 ]

20 голосов
/ 07 января 2013

Вот как функция библиотеки 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

БОНУС (от Восторг хакера ): Если вы быстро умножите на +1 и -1, следующее даст вам abs(x):

      ((x >>> 30) | 1) * x
17 голосов
/ 14 апреля 2010

Если это сборка x86, то следующая согласно когда-либо полезной википедии должна работать. Вычтите одно значение из другого и затем используйте следующие инструкции для результата:

cdq
xor eax, edx
sub eax, edx
14 голосов
/ 14 апреля 2010

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

Старая тема, но если бы я занялся серфингом здесь поздно, вы могли бы тоже ... 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
4 голосов
/ 16 апреля 2010

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

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

Команда sub устанавливает флаги так же, как инструкция cmp.

4 голосов
/ 14 апреля 2010

Предполагая, что ваши целые числа находятся в регистрах MMX или XMM, используйте psubd для вычисления разницы, затем pabsd, чтобы получить абсолютное значение разности.

Если ваши целые числа находятся в простых, "нормальных" регистрах, то выполните вычитание, а затем трюк cdq, чтобы получить абсолютное значение. Это требует использования определенных регистров (cdq sign-extends eax в edx, без использования других регистров), поэтому вы можете захотеть сделать что-то с другими кодами операций. E.g.:

mov  r2, r1
sar  r2, 31

вычисляет в регистре r2 расширение знака r1 (0, если r1 положительно или равно нулю, 0xFFFFFFFF, если r1 отрицательно). Это работает для всех 32-битных регистров r1 и r2 и заменяет инструкцию cdq.

1 голос
/ 10 апреля 2014

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 голосов
/ 14 апреля 2010

Есть инструкция SUB, если вы хотите сделать A-B. НТН

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...