Assembly - JG/JNLE/JL/JNGE after CMP

Addition and subtraction in two's complement is the same for signed and unsigned numbers

The key observation is that CMP is basically subtraction, and:

In two's complement (integer representation used by x86), signed and unsigned addition are exactly the same operation

This allows for example hardware developers to implement it more efficiently with just one circuit.

So when you give input bytes to the x86 ADD instruction for example, it does not care if they are signed or not.

However, ADD does set a few flags depending on what happened during the operation:

  • carry: unsigned addition or subtraction result does not fit in bit size, e.g.: 0xFF + 0x01 or 0x00 - 0x01

    For addition, we would need to carry 1 to the next level.

  • sign: result has top bit set. I.e.: is negative if interpreted as signed.

  • overflow: input top bits are both 0 and 0 or 1 and 1 and output inverted is the opposite.

    I.e. signed operation changed sigedness in an impossible way (e.g. positive + positive or negative

We can then interpret those flags in a way that makes comparison match our expectations for signed or unsigned numbers.

This interpretation is exactly what JA vs JG and JB vs JL do for us!

Code example

Here is GNU GAS a code snippet to make this more concrete:

/* 0x0 ==
 *
 * * 0 in 2's complement signed
 * * 0 in 2's complement unsigned
 */
mov $0, %al

/* 0xFF ==
 *
 * *  -1 in 2's complement signed
 * * 255 in 2's complement unsigned
 */
mov $0xFF, %bl

/* Do the operation "Is al < bl?" */
cmp %bl, %al

Note that AT&T syntax is "backwards": mov src, dst. So you have to mentally reverse the operands for the condition codes to make sense with cmp. In Intel syntax, this would be cmp al, bl

After this point, the following jumps would be taken:

  • JB, because 0 < 255
  • JNA, because !(0 > 255)
  • JNL, because !(0 < -1)
  • JG, because 0 > -1

Note how in this particular example the signedness mattered, e.g. JB is taken but not JL.

Runnable example with assertions.

Equals / Negated versions like JLE / JNG are just aliases

By looking at the Intel 64 and IA-32 Architectures Software Developer's Manuals Volume 2 section "Jcc - Jump if Condition Is Met" we see that the encodings are identical, for example:

Opcode  Instruction  Description
7E cb   JLE rel8     Jump short if less or equal (ZF=1 or SF ≠ OF).
7E cb   JNG rel8     Jump short if not greater (ZF=1 or SF ≠ OF).

Wikibooks has a fairly good summary of jump instructions. Basically, there's actually two stages:

cmp_instruction op1, op2

Which sets various flags based on the result, and

jmp_conditional_instruction address

which will execute the jump based on the results of those flags.

Compare (cmp) will basically compute the subtraction op1-op2, however, this is not stored; instead only flag results are set. So if you did cmp eax, ebx that's the same as saying eax-ebx - then deciding based on whether that is positive, negative or zero which flags to set.

More detailed reference here.


When you do a cmp a,b, the flags are set as if you had calculated a - b. Then the conditional jump instructions check those flags to see if the jump should be made.

In other words, the first block of code you have (with my comments added):

cmp  al, dl     ; set flags based on the comparison.
jg   label1     ; then jump based on the flags.

would jump to label1 if and only if al was greater than dl.

You're probably better off thinking of it as al > dl but the two choices you have there are mathematically equivalent:

al          >   dl
(al - dl)   >   (dl - dl) [subtract dl from both sides]
(al - dl)   >   0        [cancel the terms on the right hand side]

You need to be careful when using jg inasmuch as it assumes your values were signed. So, if you compare the bytes 101 (101 in two's complement) with 200 (-56 in two's complement), the former will actually be greater. If that's not what was desired, you should use the equivalent unsigned comparison.

See here for more detail on jump selection, reproduced below for completeness, in the order of:

  • ones that aren't signed or unsigned compares when used after CMP.
  • unsigned ones.
  • signed ones.
Instruction Description Signed-ness Conditions checked
JO Jump if overflow OF = 1
JNO Jump if not overflow OF = 0
JS Jump if sign SF = 1
JNS Jump if not sign SF = 0
JE
JZ
Jump if equal
Jump if zero
ZF = 1
JNE
JNZ
Jump if not equal
Jump if not zero
ZF = 0
JP
JPE
Jump if parity
Jump if parity even
PF = 1
JNP
JPO
Jump if no parity
Jump if parity odd
PF = 0
JCXZ
JECXZ
JRCXZ
Jump if CX is zero
Jump if ECX is zero
Jump if RCX is zero
CX = 0
ECX = 0
RCX = 0
JB
JNAE
JC
Jump if below
Jump if not above or equal
Jump if carry
unsigned CF = 1
JNB
JAE
JNC
Jump if not below
Jump if above or equal
Jump if not carry
unsigned CF = 0
JBE
JNA
Jump if below or equal
Jump if not above
unsigned CF = 1 or ZF = 1
JA
JNBE
Jump if above
Jump if not below or equal
unsigned CF = 0 and ZF = 0
JL
JNGE
Jump if less
Jump if not greater or equal
signed SF <> OF
JGE
JNL
Jump if greater or equal
Jump if not less
signed SF = OF
JLE
JNG
Jump if less or equal
Jump if not greater
signed ZF = 1 or SF <> OF
JG
JNLE
Jump if greater
Jump if not less or equal
signed ZF = 0 and SF = OF

The command JG simply means: Jump if Greater. The result of the preceding instructions is stored in certain processor flags (in this it would test if ZF=0 and SF=OF) and jump instruction act according to their state.