x86程序集abs()实现?

时间:2010-04-14 16:29:01

标签: assembly x86

我需要得到2个有符号整数的区别。是否有x86汇编语言的ABS()函数,所以我可以这样做。任何帮助将不胜感激。

8 个答案:

答案 0 :(得分:20)

这是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等于x的1的补码。现在我们只需要添加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

BONUS(来自Hacker's Delight):如果您快速乘以+1和-1,以下内容将为您提供abs(x)

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

答案 1 :(得分:17)

如果是x86程序集,则以下according to the ever useful wikipedia应该有效。从另一个值中减去一个值,然后在结果上使用这些指令:

cdq
xor eax, edx
sub eax, edx

答案 2 :(得分:14)

如果要正确处理所有情况,则不能只减去然后取绝对值。您将遇到麻烦,因为两个有符号整数的差异不一定表示为有符号整数。例如,假设您使用的是32位2s补码整数,并且您希望找到INT_MAX0x7fffffff)和INT_MIN0x80000000)之间的差异。减法给出:

0x7fffffff - 0x80000000 = 0xffffffff

-1;当你取绝对值时,得到的结果是1,而两个数字之间的实际差异是0xffffffff被解释为无符号整数(UINT_MAX)。

两个有符号整数之间的差值总是可以表示为无符号整数。要获得此值(使用2s补码硬件),只需从较大的输入中减去较小的输入,并将结果解释为无符号整数;不需要绝对值。

假设两个整数位于eaxedx中,这是x86中的一个(很多,但不一定是最好的)方法:

    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

答案 3 :(得分: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

答案 4 :(得分:4)

假设你的整数在MMX或XMM寄存器中,使用psubd计算差值,然后pabsd得到差值的绝对值。

如果你的整数在普通的“普通”寄存器中,那么进行减法,然后用cdq技巧获得绝对值。这需要使用一些特定的寄存器(cdq符号扩展eaxedx,不使用其他寄存器),因此您可能希望使用其他操作码。 E.g:

mov  r2, r1
sar  r2, 31

在寄存器r2中计算r1的符号扩展名(如果r1为正或0,则为0;如果r1为负,则为0xFFFFFFFF)。这适用于所有32位寄存器r1r2,并替换cdq指令。

答案 5 :(得分:4)

一种简短但直截了当的方式,使用条件移动指令(我认为Pentium可用):

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

子指令将标志设置为与cmp指令相同。

答案 6 :(得分: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,它的工作方式相同。

答案 7 :(得分:0)

如果您想要的是A-B,则有SUB指令。 HTH