Are binary values stored in 2's complement form by default ?
I experimented with the code mov al, -1
and saw that EAX = 000000FF
Is this by default or we could specify to use one's complement or other format

- 328,167
- 45
- 605
- 847

- 35
- 4
-
Also after this I did `add al, 10` and the `CY=1` so the carry flag was set to 1. As per what I understand if the most significand bit position results in a carry then carry flag is set. So here since I am doing add al, 10 the most significand position is considered to be the most significand bit position of al rather than EAX – shivam Jun 09 '21 at 10:23
-
1From Intel's manual (section **4.2.1 Integers**): _"The Intel 64 and IA-32 architectures define two types of integers: unsigned and signed. Unsigned integers are ordinary binary values ranging from 0 to the maximum positive number that can be encoded in the selected operand size. Signed integers are two’s complement binary values that can be used to represent both positive and negative integer values"_. And then further down: _"All operations on signed integers assume a two's complement representation."_ – Michael Jun 09 '21 at 10:34
-
When an immediate number occurs in instruction, for instance **+5**, I mentally visualize it with unlimited unsignificant leading zeroes: **...000000000005h**. Negative numbers, say **-5** I imagine with unlimited unsignificant leading F's: **...FFFFFFFFFFFFFFBh**. Only when loaded to a register or memory with fixed width, they will be trimmed. – vitsoft Jun 09 '21 at 12:08
1 Answers
x86 hardware uses 2's complement signed integers e.g. for instructions like movsx
sign-extension, imul
/idiv
signed multiply/divide, and FLAGS setting (specifically OF) for add
etc, including sub
/cmp
and branch conditions like jle
less-or-equal. And there are no one's complement math instructions (except for not
, one's complement negation, vs. neg
two's complement negation aka binary 0 - x
.)
See also Understanding Carry vs. Overflow conditions/flags which describes exactly how 2's complement overflow (OF) vs. carry-out (CF) work for addition.
Assemblers always1 use 2's complement when encoding negative numbers in the source into machine code. mov
-immediate in machine code just copies bit-pattern into the register; all "interpretation" is already done before the CPU sees it. (The only case of mov reg, sign_extended_narrow_immediate
is x86-64 mov r/m64, imm32
in 64-bit mode only.) Also note that mov al, -1
doesn't affect the upper bits of EAX. If you saw 0x000000FF
, that's because EAX's upper bytes happened to already be zero.
Footnote 1: You could of course write an x86 assembler that was really weird and did something else. It would be unlikely anyone would would want to use it, though, because it would mean that add eax, -2
didn't decrease EAX's value by 2. Existing mainstream assemblers use the same number format as the hardware, and the hardware is hard-wired for 2's complement, not switchable.
old_timer points out that some assemblers (e.g. simple ones for simple microcontrollers) might not even support syntax for negative constants at all, in which case you'd always have to manually encode constants into hex or whatever. Things like 0xFF
or $FF
or whatever syntax.
If you want to use 1's complement bit-patterns, encode them manually into hex. e.g. mov al, 0FDh
(~2
) instead of mov al, 0FEh
or -2
.
And of course you'd have to implement 1's complement math using multiple instructions. add
does binary addition, which is the same operation as 2's complement signed addition, but not the same operation as 1's complement. (That's a major reason why computers use 2's complement: +/- are the same operation as unsigned, and so is the low half of a multiply.)
Note that x86 machine code has some forms of instructions like add r/m32, sign_extended_imm8
which involves 2's complement sign extension in decoding. i.e. the upper 24 bits are copies of bit #7, replicating the top bit of the immediate to fill the register. Many 1's complement values are compatible with this, e.g. add eax, 0FFFFFFFDh
can be encoded as an imm8, and assemblers will do that for you.

- 328,167
- 45
- 605
- 847
-
"Assemblers always use 2's complement when encoding negative numbers in the source into machine code." Is by definition not true. Assembly language is specific to the assembler not the target, and it is the design choice of the assembler author as to how to interpret numbers and convert them. It might not be wise, and they may have no users but it only takes one that I could write in an afternoon and post online, to demonstrate this statement is false. – old_timer Jun 09 '21 at 11:21
-
I am not sure that every assembler I have used allows for negative number notation in general, if even decimal numbers (or certainly not all support hex for that matter). – old_timer Jun 09 '21 at 11:22
-
@old_timer: Fair enough. Added a footnote to expand on that point, which really gets to the heart of the question. – Peter Cordes Jun 09 '21 at 11:22
-
-
@old_timer: If you think your answer is a better explanation that mine (or good alternative / 2nd take on things), undelete it and let voters decide. When I initially skimmed it, I did miss some of the opening paragraph which is relevant. (Unlike the example of how 2's complement addition works, which think isn't relevant, and is mostly what I was looking at when I commented on it.) – Peter Cordes Jun 09 '21 at 11:33
-
Need one more info on this : `mov al, 1` `sub al, 100` I did these instructions and found the carry flag to be set. Now 1 = 00000001 and -100 = 10011100 in 2's complement form so when I add 1 and -100 I don't see why there will be a carry – shivam Jun 09 '21 at 13:10
-
1@shivam: Carry Flag is for unsigned under/overflow. 1 minus 100 underflows in an unsigned subtraction so Carry Flag (borrow, really) is set. – ecm Jun 09 '21 at 13:14
-
But isn't subtraction done like addition in the ALU so like 1-100 = 00000001 + 10011100. Is that correct ? I saw something about it in the earlier mentioned link : http://teaching.idallen.com/dat2343/10f/notes/040_overflow.txt which mentions the carry flag is also set if there is a borrow in the most significand bit. But a borrow to me implies that the subtraction is not internally as I have mentioned above... – shivam Jun 09 '21 at 13:21
-
1@shivam: No, CF is set as a borrow output from subtraction, not as a carry-out from adding `-x`. Some machines (like ARM) have a carry flag that's set exactly opposite of x86's (~borrow), but that's still not carry-out from adding `-x`. There are a couple corner cases where that method would differ, see [Arithmetic identities and EFLAGS](https://stackoverflow.com/a/62219965) – Peter Cordes Jun 09 '21 at 13:22