Home · part 7 of 9
Signed or unsigned: who decides?
The hardware stores no sign. One compare sets four flags, and the next instruction chooses which of them to read. Here that choice is made visible: every ARM branch on one compare, then the C and assembly of a real if, traced for any pair of bytes.
1. Who decides
Nothing in the Bit Lab's circuit knows whether A is 128 or −128. The register holds eight bits and the adder adds them. Neither has anywhere to store which reading you meant.
It does not need to. The ALU produces one result and all four flags on every operation. C and Z answer the unsigned questions and N and V the signed ones, so both readings are available at the same time.
The decision is made afterwards, by the next instruction. A compare instruction, CMP, is a subtraction that keeps the flags and throws the result away. After it comes a branch instruction, which tests some of the flags and jumps if the test passes. The instruction set has two branches for every question: one tests the unsigned flags and one tests the signed flags. Whoever writes the program, or the compiler working for them, picks which one to use. That is who decides.
Try it below. Pick two bytes, or tap their bits, and they are fed through CMP A, B; the snippet ends with BEQ, the one branch both readings share. The tables underneath list the other branches: what each one means, which flags it tests, whether it would be taken right now, and whether that matches the arithmetic under its reading. Change A or B here at any time.
2. One compare, every branch
Unsigned reading
Signed reading
ARM mnemonics. x86 has the same pairs under other names: jb/jae/ja/jbe for unsigned and jl/jge/jg/jle for signed, except that x86 sets its carry flag to 1 when a borrow was needed, the opposite of ARM.
3. The same thing as a program
Here is the same comparison as code: one if in C, and the ARM assembly a compiler turns it into. Pick which type a and b are declared as. The only C line that changes is the declaration, yet the compiler emits a different load and a different branch, because the type tells it which member of each pair to use. The lines that actually run for the current A and B are highlighted, and the trace underneath walks through them.
C
ARM assembly
4. What the loads do to the bits
The two loads differ only in the 24 bits that were never in memory. On ARM every register is 32 bits wide and CMP compares whole registers, so a byte has to be widened on its way in: LDRB fills the spare bits with 0 and LDRSB fills them with copies of the byte's top bit, which is why the same byte 0x80 becomes 128 under one and −128 under the other, and why the two loads agree whenever the top bit is 0. The companion page From bytes to registers draws both loads and the widened compare cell by cell, for any pair you choose, and explains why the 32-bit flags can differ from the 8-bit ones while the branch lands the same way.
There are likewise two right shifts, two widening loads and two divides, because those are the operations where the top bit's meaning changes the answer. (A load is widening whenever the register is wider than the value in memory, which is every byte load on a 32-bit machine: the spare bits have to be filled with something, and the two loads fill them differently, as the program above shows.) A typed language records the decision once: in C, unsigned or int tells the compiler which member of each pair to emit, so you state the reading at the declaration and the compiler applies it at every operation.
That is why the Bit Lab's values table always shows both readings, and why its flag cards say "as unsigned" and "as signed": C answers whether the unsigned reading of the result fits, V whether the signed one does, and the same pattern can be right under one and wrong under the other. Load 0x80 vs 0x7F and watch the two readings order the same bits oppositely.
To make this automatic, the flag drills page gives randomised predict-then-check exercises on exactly these four things: the flags of a CMP, which branches fire, recovering the type from a compiled if, and widening a byte by hand.