Greg C.
Greg C.

Reputation: 341

x86 assembly abs() implementation?

Is there an abs() function in x86 assembly language?

(This question originally mentioned getting the difference of 2 signed integers, but that's really a separate question if you need to do that without possible signed-overflow in the subtraction. Otherwise yes, just abs(x-y), possibly widening inputs first.)

Upvotes: 34

Views: 51096

Answers (9)

bits
bits

Reputation: 1705

This is how the C library function abs() does it in assembly without branching:

   abs(x) = (x XOR y) - y

where y = x >> 31 (assuming 32-bit input), and >> is arithmetic right shift operator.

Explanation of the above formula: We want to generate 2's complement of negative x only.

y = 0xFFFFFFFF, if x is negative
    0x00000000, if x is positive

So when x is positive x XOR 0x00000000 is equal to x . And when x is negative x XOR 0xFFFFFFFF is equal to 1's complement of x. Now we just need to add 1 to get its 2's complement which is what expression -y is doing . Because 0xFFFFFFFF is -1 in decimal.

Let's look at assembly generated for following code by gcc (4.6.3 on my machine):

C code:

main()
{
  int x;
  int output = abs(x);
}

gcc 4.6.3 generated assembly snippet (AT&T syntax), with my comments:

  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 (from Hacker's Delight): If you have a fast multiply by +1 and -1, the following will give you abs(x) for 32-bit x:

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

Upvotes: 41

user17680842
user17680842

Reputation:

8 chars at once

inline u64 abs_8(u64 x)
{
    u64 y=(x>>7)&0x0101010101010101ull;
    return (x^((y<<8)-y))+y;
}

Upvotes: 0

Pr0c3ss0r
Pr0c3ss0r

Reputation: 159

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:

If the flags are already set by whatever generated the value in eax, you don't need the test. Branch mispredicts will make this slow if the input values are randomly distributed between positive and negative.

This works the same way for RAX, AX, AL.

Upvotes: 2

Hal
Hal

Reputation: 321

Old thread but if I surfed in here late you might have too... abs is a brilliant example so this should be here.

; 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

Upvotes: 32

Callum
Callum

Reputation: 862

A short but straightforward way, using the conditional move instruction (available Pentium and up I think):

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

The sub instruction sets the flags the same as the cmp instruction.

Upvotes: 5

Stephen Canon
Stephen Canon

Reputation: 106287

If you want to handle all cases correctly, you can't just subtract and then take the absolute value. You will run into trouble because the difference of two signed integers is not necessarily representable as a signed integer. For example, suppose you're using 32 bit 2s complement integers, and you want to find the difference between INT_MAX (0x7fffffff) and INT_MIN (0x80000000). Subtracting gives:

0x7fffffff - 0x80000000 = 0xffffffff

which is -1; when you take the absolute value, the result you get is 1, whereas the actual difference between the two numbers is 0xffffffff interpreted as an unsigned integer (UINT_MAX).

The difference between two signed integers is always representable as an unsigned integer. To get this value (with 2s complement hardware), you just subtract the smaller input from the larger and interpret the result as an unsigned integer; no need for an absolute value.

Here's one (of many, and not necessarily the best) way do this on x86, assuming that the two integers are in eax and 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

Upvotes: 17

Thomas Pornin
Thomas Pornin

Reputation: 74492

Assuming that your integers are in MMX or XMM registers, use psubd to compute the difference, then pabsd to get the absolute value of the difference.

If your integers are in the plain, "normal" registers, then do a subtraction, then the cdq trick to get the absolute value. This requires using some specific registers (cdq sign-extends eax into edx, using no other register) so you may want to do things with other opcodes. E.g.:

mov  r2, r1
sar  r2, 31

computes in register r2 the sign-extension of r1 (0 if r1 is positive or zero, 0xFFFFFFFF if r1 is negative). This works for all 32-bit registers r1 and r2 and replaces the cdq instruction.

Upvotes: 6

Aidenn
Aidenn

Reputation: 559

There is the SUB instruction, if what you want is to do A-B. HTH

Upvotes: -2

Mark Wilkins
Mark Wilkins

Reputation: 41252

If it is x86 assembly, the following according to the ever useful wikipedia should work. Subtract one value from the other and then use these instructions on the result:

cdq
xor eax, edx
sub eax, edx

Upvotes: 20

Related Questions