Testing on EMU8086, with the following code snippet:
MOV CX, 1527H
SUB CX, 44H
The emulator shows that AF is 0
1527
- 44
========
14E3
When doing the subtraction by hand, we got 7 - 4 = 3, no problem here. Then 2 - 4, we then have to borrow from the next nibble. So from my understanding, AF should have been 1.
AF is set according to carry (or borrow) from bit #3 to bit #4. i.e. across the lowest / least-significant nibble boundary, the one in the middle of AL/BL/CL/DL, not the middle of AX. (Since each hex digit represents a nibble, carry/borrow from the lowest hex digit into the 2nd-lowest.)
As you say
7h - 3h
doesn't borrow, so AF=0.The description of AF as a "half-carry" flag makes sense in the context of byte operand-size, where there's only one nibble boundary within the byte, and it's half-way to the carry-out position.
Word operand-size (and larger on 386 and x86-64) still sets AF from bit 3->4, not from the middle of the operand-size or carry between any other bit-positions.
That's because it's intended for packed and unpacked BCD operations like DAA and AAA respectively. Note that AAA (for use after
add ax, cx
or whatever with 2 decimal digits unpacked into separate bytes) depends on AF detecting carry-out from the low 4 bits. There would never be carry from bit #7 to bit #8 (across the byte boundary) in that case, e.g.0x0909
+0x0909
produces0x1212
, with an AF-setting carry from9+9 = 12h
, but no carry from09h + 09h = 12h
at the byte boundary.Instead of working differently for unpacked (checking the high bits of AL),
AAA
uses mostly the same logic as for DAA (checking AF, and low nibble ofal
being > 9) - https://www.felixcloutier.com/x86/aaa#operationFun fact: you can use
DAS
to save a couple bytes in int -> ASCII-hex conversion, along with cmp and sbb, a total hack / abuse that just happens to work because of the distance between the ASCII codes for'9'
and'A'
, along with DAS's conditionalAL-=6
and other behaviour.