The idea: digits times place values
A number is written as digits, and each digit is multiplied by a place value. In base 10 the places are 1, 10, 100. In base 2 (binary) they are 1, 2, 4, 8, … 128, and the only digits are 0 and 1. In base 16 (hexadecimal) they are 1 and 16, and the digits are 0–9 and A–F (A = 10 … F = 15). The number does not change, only the way it is written.
The page has one 8-bit register x (a byte), bits b7 … b0 with their place values above them. Pick x and a base and press Convert, Negate x, or pick y and press Add x + y. On the right, a wheel of all 256 byte values shows where x sits.
Decimal to binary: divide by 2 again and again
Divide the number by 2 and write down the remainder (0 or 1). Go on with the quotient until it is 0. The first remainder is the lowest bit b0, so the remainders read bottom-up are the binary number.
156 ÷ 2 = 78 r 0 ← b0 78 ÷ 2 = 39 r 0 39 ÷ 2 = 19 r 1 19 ÷ 2 = 9 r 1 9 ÷ 2 = 4 r 1 4 ÷ 2 = 2 r 0 2 ÷ 2 = 1 r 0 1 ÷ 2 = 0 r 1 ← b7 156 = 1001 1100
Why it works: the remainder of n ÷ 2 is the 1s bit, and n ÷ 2 shifts every other bit one place to the right. To go back, add the place values of the 1 bits: 128 + 16 + 8 + 4 = 156 (Demo 1: 156 → binary). Dividing by 16 gives the hex digits the same way: 156 ÷ 16 = 9 r 12, 9 ÷ 16 = 0 r 9, so 156 = 0x9C (Demo 3).
Binary and hex: groups of 4 bits
16 = 24, so one hex digit is exactly 4 bits (a nibble). Split the bits into groups of 4 from the right and replace each group with its digit: 1001 = 9 and 1100 = 12 = C, so 1001 1100 = 0x9C (Demo 2). A byte is always two hex digits, which is why memory dumps, colours (#FFCC80) and hashes are written in hex.
| bits | hex | bits | hex | bits | hex | bits | hex |
|---|---|---|---|---|---|---|---|
| 0000 | 0 | 0100 | 4 | 1000 | 8 | 1100 | C |
| 0001 | 1 | 0101 | 5 | 1001 | 9 | 1101 | D |
| 0010 | 2 | 0110 | 6 | 1010 | A | 1110 | E |
| 0011 | 3 | 0111 | 7 | 1011 | B | 1111 | F |
Negative numbers: two's complement
A byte has 256 patterns. Unsigned, they mean 0 … 255. Signed in two's complement, they mean −128 … 127: the top bit b7 is worth −128 instead of +128, and the other bits keep their values. So 1001 1100 is 156 unsigned and −128 + 28 = −100 signed. A negative number always has b7 = 1.
To make −x: invert every bit, then add 1. 42 = 0010 1010, inverted 1101 0101 (213), plus 1 gives 1101 0110 = 214 = 0xD6, which is −42 (Demo 4). It works because x + ~x = 1111 1111 = 255, so ~x + 1 = 256 − x, and 256 is 0 in 8 bits.
The wheel and overflow
Put the 256 byte values on a wheel, 0 at the top and counting clockwise. Adding y moves y steps clockwise; adding a negative number moves back. The unsigned values wrap from 255 to 0 at the top, and that wrap is the carry out (flag C). The signed values wrap from 127 to −128 at the bottom, and crossing that edge is signed overflow (flag V).
127 + 1 is 0111 1111 + 0000 0001 = 1000 0000: 128 unsigned, but −128 signed (Demo 5). The CPU sees it from the carries: the carry into b7 is 1 and the carry out of b7 is 0. When those two differ, V = 1. Put simply, two positive numbers gave a negative result, or two negative numbers gave a positive one. In C, signed overflow is undefined behaviour; Java and most CPUs just wrap around as the wheel does.
Subtraction is adding a negative
The same adder works for signed and unsigned numbers, and subtraction needs no circuit of its own: x − y = x + (−y). 100 + (−42) is 0110 0100 + 1101 0110 = 1 0011 1010. Drop the carry out of the byte and 0011 1010 = 58 is left (Demo 6). On the wheel, adding 214 goes clockwise almost a full turn and lands 42 steps back from 100. Here C = 1 but V = 0: the signed answer is right. That is why the carry flag only matters for unsigned numbers and V only for signed ones.
| sum | bits | C | V | signed result |
|---|---|---|---|---|
| 127 + 1 | 0111 1111 + 0000 0001 = 1000 0000 | 0 | 1 | −128 ✗ |
| 100 + (−42) | 0110 0100 + 1101 0110 = 0011 1010 | 1 | 0 | 58 ✓ |
| 42 + (−42) | 0010 1010 + 1101 0110 = 0000 0000 | 1 | 0 | 0 ✓ |
Bytes, nibbles, MSB and LSB
8 bits make a byte, and half a byte (4 bits) is a nibble, which is exactly one hex digit. In 1001 1100 = 0x9C the high nibble 1001 is 9 and the low nibble 1100 is C. The leftmost bit b7 is the most significant bit (MSB, weight 128) and the rightmost bit b0 is the least significant bit (LSB, weight 1; it says whether the number is odd) (Demo 7: a byte).
n bits have 2n patterns, so unsigned they hold 0 … 2n − 1: a nibble 0 … 15, a byte 0 … 255, 32 bits 0 … 4,294,967,295. 210 = 1024 is close to 1000, so memory sizes use powers of 2: 1 KiB (kibi) = 1024 bytes and 1 MiB = 220 bytes, while 1 kB (kilo) = 1000 bytes, as disk makers count.
Bigger numbers are several bytes. The 32-bit word 0xDEADBEEF is the bytes DE AD BE EF: DE is the most significant byte and EF the least significant byte (Demo 8: the 4 bytes of a 32-bit word). Which of them is stored at the lowest memory address is the byte order; see Little-endian vs big-endian.
See also Number Systems: Two's Complement, Fixed Point and IEEE 754 Floating Point for wider integers, fixed point and how fractions are stored in bits, Base64 Encoding & Decoding for another base (64) used to write bytes as text, Arithmetic Circuits for the adder in gates, and Modular Exponentiation for more arithmetic that wraps around.
Reading digits in a base is Horner's rule; see it evaluate polynomials in Polynomial Division.
See Little-endian vs big-endian for byte order.
See also Logic Gates, where the rows of each gate's truth table count up in binary from 00…0 to 11…1.
Two's complement is what makes lowbit(i) = i & −i work: see the Fenwick tree, which walks its array by clearing and adding the lowest 1 bit.
What the page leaves out
Wider integers (16, 32 and 64 bits work the same way, with −2n−1 as the top weight); other ways to store signs (sign and magnitude, one's complement, both with two zeros); octal; sign extension when a byte is widened; the N and Z flags; multiplication and division; how multi-byte numbers are laid out in memory (only named here: see the endianness page); and fractions in binary, which are the topic of the floating point page.