assemblyx86carryflag

What is the difference between “4 - 12” and “4 + (-12)”?


I try to compare the next expressions:

1) 
    mov al, 4
    mov bl, 12
    sub al, bl ; CF == 1

    0000 0100 (4)
   +
    1111 0100 (-12)
   = 
    1111 1000 (-8 == 248) 

2) 
    mov al, 4
    mov bl, -12
    add al, bl ; CF == 0

    0000 0100 (4)
   +
    1111 0100 (-12)
   = 
    1111 1000 (-8 == 248) 

The results are identical, but carry flags are not. Why? The subtraction realized by addition to twos complement value.


Solution

  • Subtraction is not equal to addition of the two's complement on x86. To understand what value the carry flag will assume you must perform a long subtraction instead:

        0000 0100
    -   0000 1100
    -------------
      1 1111 1000
    

    See how there's a borrow left over at the end? This borrow is what sets the carry flag (i.e. carry equals borrow).

    Some other architectures like ARM do indeed implement subtraction as addition, but they don't do so as addition of the two's complement but rather as addition of the one's complement and an extra carry in. This is significant when subtracting 0.

    For example, your method would yield for 12 − 0:

        0000 1100
    +   0000 0000 (- 0000 0000 => + 0000 0000)
    -------------
      0 0000 1100
    

    with a clear carry. But what actually happens is

        0000 1100
    +   1111 1111 (- 0000 0000 => +1111 1111 + 1)
    +           1
    -------------
      1 0000 1100
    

    with a carry. This detail is important as otherwise comparisons with 0 would not work correctly. In this scheme, carry is indicated whenever there is no borrow (i.e. carry is complemented borrow).

    The way Intel does it and the way ARM does it actually always yield the same result, except the carry flag is just the other way round. So whenever ARM would set the carry, Intel would clear it and vice versa.

    Both approaches to subtraction semantics are fairly common. The ARM approach is slightly easier to implement as it allows direct use of the adder for subtraction without having to touch the carry at all. With the Intel approach, carry in and out have to be complemented when a subtraction is performed, but the extra gates to do so really don't matter in the grand scheme of things. On the other hand, Intel's approach is more intuitive to programmers as thinking of the carry flag as also indicating borrow makes more sense if you visualise the operation being performed as being a long subtraction.