Understanding Number Bases
Binary, octal, and hexadecimal explained: how positional counting systems work, why hex maps cleanly to bits, and where each base shows up in real code.
A number base (or radix) is how many distinct digits a counting system uses before it carries over to the next column. Decimal — the system taught in school — is base 10: ten digits, 0 through 9, and the column after 9 rolls over to 10. Binary, octal, and hexadecimal follow the exact same positional logic with a different digit count.
Computers do not have a native concept of decimal. Every value in memory is a sequence of on/off switches — binary — and octal and hexadecimal exist purely as more compact, human-readable ways to write down binary values without losing the exact bit pattern.
The three bases that matter
| Base | Digits used | Bits per digit | Where you'll see it |
|---|---|---|---|
| Binary (base 2) | 0 1 | 1 | The actual bit pattern in memory — everything else is a shorthand for this |
| Octal (base 8) | 0–7 | 3 | Unix file permissions (chmod 755) |
| Hexadecimal (base 16) | 0–9, A–F | 4 | Colors, memory addresses, byte values, hashes, UUIDs |
Hex won over octal for most modern uses because a byte (8 bits) splits evenly into two 4-bit hex digits, but not evenly into 8/3 octal digits — hex maps to bit patterns more cleanly.
Conversion table: 0 to 15
| Decimal | Binary | Octal | Hex |
|---|---|---|---|
| 0 | 0000 | 0 | 0 |
| 1 | 0001 | 1 | 1 |
| 2 | 0010 | 2 | 2 |
| 7 | 0111 | 7 | 7 |
| 8 | 1000 | 10 | 8 |
| 9 | 1001 | 11 | 9 |
| 10 | 1010 | 12 | A |
| 15 | 1111 | 17 | F |
Past 9, hexadecimal keeps counting with letters: A = 10, B = 11, up to F = 15. One hex digit always covers exactly the same range as 4 binary digits, which is the whole reason it is convenient — 15 is the largest value 4 bits can hold, and F is the largest single hex digit.
Converting hex to binary is just substitution
Because one hex digit is exactly 4 bits, converting hex to binary (or back) needs no arithmetic — just replace each digit with its 4-bit pattern from the table above:
0xC0 → 1100 0000
0xA8 → 1010 1000
0xFF → 1111 1111Decimal does not divide evenly into binary this way, which is why converting decimal to binary needs actual division (repeatedly dividing by 2 and reading the remainders bottom-up), while hex-to-binary is pure lookup.
Reading number literals in code
Most C-family languages (JavaScript, Python, Rust, Go) recognize the same prefixes for non-decimal literals:
0xFF // hexadecimal — 255
0b11111111 // binary — 255
0o377 // octal — 255
1_000_000 // underscore as a digit separator (readability only) C and C++ are the outliers: a bare leading zero means octal (0755, not 0o755), a frequent source of bugs when a decimal number is accidentally zero-padded and silently reinterpreted.
Where each base shows up in practice
- Unix permissions —
chmod 755means ownerrwx(7 = 111), groupr-x(5 = 101), othersr-x(5 = 101). Octal exists here because 3 bits maps exactly to the 3 permission flags (read/write/execute). - CSS/HTML colors —
#RRGGBB, each channel one byte (00–FF) in hex. - Network addresses —
192.168.1.1is0xC0A80101as a 32-bit hex integer; subnet masks like255.255.255.0are 24 binary ones followed by 8 zeros. - Character encodings — ASCII and Unicode code points are conventionally written in hex: the letter
Ais0x41(decimal 65). - Hashes and UUIDs — a SHA-256 digest or a UUID is displayed as hex because the raw value is binary and hex is the standard compact text representation for it.
Frequently asked questions
Hex needs 16 distinct digits per position, but decimal only supplies 10 (0–9). Letters A through F stand in for the values 10 through 15, so every value a 4-bit group can hold (0–15) has a single-character digit.
This is what makes hex convenient: one hex digit always represents exactly 4 bits, with no digit ever needing two characters.