Every number a computer holds is a row of bits. People rarely read those rows directly: they read hexadecimal, a shorthand that turns four bits into one character. Negative numbers add one more layer, two's complement, which explains why the same byte can be 255 or −1 depending on who reads it. This article works through each step by hand with real numbers, then lists the mistakes that cause most of the confusion. Every result below can be checked in the number base converter.
A base is just a different size of bundle
In decimal each position is worth ten times the one to its right: 156 is 1 × 100 + 5 × 10 + 6. In base 2 each position is worth twice the one to its right, so the positions are worth 1, 2, 4, 8, 16, 32, 64, 128 and so on. In base 16 they are worth 1, 16, 256, 4096. Nothing about the number changes; only the bundle size does.
Base 16 needs sixteen digit symbols, so after 9 come A (10), B (11), C (12), D (13), E (14) and F (15). Bases up to 36 continue through the alphabet to Z (35), which is why base 36 is sometimes used for short identifiers.
Decimal to binary by repeated division
To write 156 in binary, divide by 2 and keep the remainders:
| Step | Quotient | Remainder |
|---|---|---|
| 156 ÷ 2 | 78 | 0 |
| 78 ÷ 2 | 39 | 0 |
| 39 ÷ 2 | 19 | 1 |
| 19 ÷ 2 | 9 | 1 |
| 9 ÷ 2 | 4 | 1 |
| 4 ÷ 2 | 2 | 0 |
| 2 ÷ 2 | 1 | 0 |
| 1 ÷ 2 | 0 | 1 |
Read the remainders from the bottom up: 1001 1100. Check it the other way: 128 + 16 + 8 + 4 = 156. The same method works for any base: divide by 16 and 156 gives quotient 9, remainder 12, so 156 is 9C in hex.
Why programmers read hex instead of binary
Sixteen is 2⁴, so one hex digit stands for exactly four bits, never more, never less. Converting between binary and hex needs no arithmetic at all: split the bits into groups of four from the right and replace each group.
- 1001 1100 → 1001 is 9, 1100 is 12 (C) → 9C
- 0x1E90FF → 0001 1110 1001 0000 1111 1111
A byte is eight bits, so it is always exactly two hex digits, from 00 to FF. That is why colours are written as #1E90FF (red 0x1E = 30, green 0x90 = 144, blue 0xFF = 255), why memory addresses and file signatures are shown in hex, and why a SHA-256 hash is 64 hex characters long (256 bits ÷ 4). Decimal does not line up with bits: 255 and 256 look nothing alike in decimal, but FF and 100 make the byte boundary obvious.
Octal (base 8) groups bits in threes. It survives mostly in Unix file permissions: 755 is 111 101 101, read, write and execute for the owner, read and execute for the others.
Two's complement: how negative numbers are stored
A fixed number of bits has a fixed number of patterns: 8 bits give 256 of them. Read as unsigned, they mean 0 to 255. To get negative numbers, two's complement says: every pattern whose top bit is 1 stands for its unsigned value minus 256. So the signed range of a byte is −128 to 127.
The recipe for writing a negative number by hand is "invert the bits and add one". Take −42 in 8 bits:
- 42 in binary is 0010 1010.
- Invert every bit: 1101 0101.
- Add one: 1101 0110, which is 0xD6.
Check: 0xD6 is 214 unsigned, and 214 − 256 = −42. Even better, add the two patterns: 0010 1010 + 1101 0110 = 1 0000 0000. The ninth bit falls off the end of the byte and what remains is 0, exactly as 42 + (−42) should be. That is the whole point of two's complement: the processor adds signed and unsigned numbers with the same circuit.
Two more consequences are worth knowing:
- Overflow wraps around. 127 + 1 in a signed byte is 0111 1111 + 1 = 1000 0000, which is −128. Nothing warns you in most languages.
- Widening copies the sign bit. −42 as a 16-bit number is 0xFFD6, not 0x00D6. Copying ones into the new high bits (sign extension) keeps the value −42; filling with zeros (zero extension) would turn it into 214.
The bit pattern of a decimal fraction
Fractions follow the same rule in reverse: each position after the point is worth half the previous one (½, ¼, ⅛ ...). A fraction has a short binary form only if its denominator is a power of two. 0.5 is 0.1 in binary, 0.75 is 0.11, but 0.1 is 1/10 and 10 contains the factor 5, so its binary digits never end: 0.0(0011), the block 0011 repeating forever. A 32-bit float has to stop somewhere, so 0.1 is stored as 0.100000001490116119384765625 (bits 3DCCCCCD). The tool's float tab shows every digit of the stored value and the difference from what you typed.
Common mistakes
- Reading hex as decimal. "10" in hex is sixteen. When a value is written without a prefix, ask which base it is in; 0x10, 10h and the subscript 10₁₆ all mean sixteen.
- Forgetting the width. "What is −1 in binary?" has no answer until you say how many bits: 1111 1111 in 8 bits, sixteen ones in 16 bits. The same pattern FF is 255 or −1 depending on whether the type is signed.
- Taking a range literally. 128 does not fit a signed byte; −128 does. The asymmetry is real: there is one more negative number than positive ones.
- Zero-extending a negative number. Moving a signed byte into a wider integer with zeros on top silently changes −42 into 214.
- Leading zeros that mean octal. In C, 0777 is octal (511 in decimal). Python 3 rejects it and wants 0o777, while JavaScript's
parseInt("0777")reads 777. Write the prefix you mean. - Expecting 0.1 to be exact. It is not, in any binary float. Compare floats with a tolerance, or use decimal or integer arithmetic for money.
Try it
Type 156 in the number base converter to see 1001 1100 and 9C, switch to the two's complement tab and enter −42 to watch the invert-and-add-one steps, then open the float tab to see why 0.1 + 0.2 is not quite 0.3. The scientific calculator handles the arithmetic around it.