Number bases, and why programmers use them
How positional notation works in any base, why hexadecimal maps so cleanly onto binary, and where each base actually shows up.
Last reviewed · 1,229 words
In short
- Every base works the same way. The digit's position gives it a power of the base, and only the base changes.
- One hexadecimal digit is exactly four binary digits, which is why hex is used to write binary compactly.
- 255 is 11111111 in binary and FF in hex — the largest value in one byte, which is why it appears everywhere.
- Octal survives mainly in Unix file permissions, where each digit encodes three permission bits.
- A leading zero makes a number octal in several languages, which is a real source of bugs.
A number base is how many distinct digits the system has before it needs a second column. Decimal has ten, binary two, hexadecimal sixteen.
Everything else follows from that one fact.
Positional notation
In any base, a digit's value is the digit multiplied by the base raised to the power of its position, counting from zero at the right.
Decimal 1234 is 1×10³ + 2×10² + 3×10¹ + 4×10⁰ = 1000 + 200 + 30 + 4.
Binary 1011 is 1×2³ + 0×2² + 1×2¹ + 1×2⁰ = 8 + 0 + 2 + 1 = 11.
Hexadecimal 2F is 2×16¹ + 15×16⁰ = 32 + 15 = 47.
Hexadecimal needs sixteen digits, so it borrows letters: A=10, B=11, C=12, D=13, E=14, F=15.
Why hexadecimal exists
Binary is what computers use and it is unreadable at length. The byte 11010110 is eight characters of nearly identical shape, and a human comparing two of them will make mistakes.
Hexadecimal fixes this because 16 is 2⁴, so one hex digit is exactly four binary digits with no remainder:
| Binary | Hex | Decimal |
|---|---|---|
| 0000 | 0 | 0 |
| 0101 | 5 | 5 |
| 1001 | 9 | 9 |
| 1010 | A | 10 |
| 1111 | F | 15 |
11010110 splits into 1101 and 0110, which are D and 6. The byte is D6 — two characters instead of eight, and the conversion is a lookup rather than arithmetic.
That clean four-to-one mapping is the entire reason hexadecimal is used. Base 10 has no such relationship with base 2, which is why decimal is inconvenient for anything working at the bit level.
The numbers that keep appearing
| Decimal | Binary | Hex | Why it matters |
|---|---|---|---|
| 8 | 1000 | 8 | Bits in a byte |
| 15 | 1111 | F | Largest single hex digit |
| 16 | 10000 | 10 | Hex base |
| 127 | 1111111 | 7F | Largest signed 8-bit value; ASCII range |
| 128 | 10000000 | 80 | Most significant bit set |
| 255 | 11111111 | FF | Largest unsigned byte |
| 256 | 100000000 | 100 | Values in one byte |
| 1024 | 10000000000 | 400 | 2¹⁰, the "kilo" in kibibyte |
| 65535 | — | FFFF | Largest unsigned 16-bit value |
255 and FF are worth recognising on sight. A byte holds 256 values, 0 to 255, which is why colour channels run 0–255, why IP address octets stop at 255, and why FF appears at the end of so many hex strings.
Colour codes are three bytes: #FF5733 is red FF (255), green 57 (87), blue 33 (51). #FFFFFF is white — all channels at maximum. #000000 is black.
Where each base is used
Binary — the machine level. Bitwise operations, flags packed into a single integer, network masks, file permission bits.
Octal, base 8 — largely obsolete, with one survivor: Unix file permissions. chmod 755 works because each digit is three bits, one each for read, write and execute. 7 is 111, so read, write and execute; 5 is 101, so read and execute. 755 is therefore full permission for the owner and read-plus-execute for everyone else.
Decimal — everything a person reads.
Hexadecimal — memory addresses, colour codes, MAC addresses, Unicode code points (U+0939), checksums, and any dump of binary data.
Base64 is not a number base in this sense. It encodes arbitrary binary data as text using 64 printable characters, and it is used for embedding images in HTML and attaching files to email. It makes data 33% larger.
The leading zero trap
In C, Java, older JavaScript and several other languages, a numeric literal starting with 0 is octal.
010 is 8, not 10. 0755 is 493 in decimal.
This bites in practice where numbers are entered with leading zeros for alignment or from a form: a month written as 08 or 09 is not even valid octal, since octal has no digits 8 or 9, and produces either an error or a silent zero depending on the language.
Modern JavaScript rejects the old syntax in strict mode and uses the explicit prefixes instead. The current prefixes are 0b for binary, 0o for octal and 0x for hexadecimal, and using them removes the ambiguity entirely.
Converting by hand
To decimal: multiply each digit by the base raised to its position and add.
From decimal: divide repeatedly by the base, collecting remainders, and read them bottom to top. 47 ÷ 16 = 2 remainder 15, then 2 ÷ 16 = 0 remainder 2. Reading upward: 2, 15 — which is 2F.
Binary to hex: group the binary digits into fours from the right, padding the left with zeros, and convert each group. This is the only conversion worth doing mentally, and it is the one that comes up most.
Hex to binary: the same in reverse, each hex digit becoming four bits.
Signed numbers and two's complement
Negative numbers in binary use two's complement: invert every bit and add one.
In 8 bits, −1 is 11111111 — the same pattern as 255 unsigned. Whether that byte means −1 or 255 depends entirely on how the program interprets it, and nothing in the bits themselves says which.
This is why an 8-bit signed integer runs from −128 to 127 rather than −127 to 127: there is one more negative value than positive, because zero occupies a slot on the positive side.
It is also the mechanism behind integer overflow. Adding 1 to the largest positive value wraps to the largest negative one, which is a real and occasionally catastrophic class of bug.
Bitwise operations, briefly
Working in binary is what makes bit manipulation legible, and a handful of operations cover most uses.
AND (&) produces 1 where both bits are 1. Used to test a flag or mask off
bits: value & 0xFF keeps only the lowest byte.
OR (|) produces 1 where either bit is 1. Used to set a flag.
XOR (^) produces 1 where exactly one bit is 1. Used to toggle, and it is its
own inverse — applying the same XOR twice returns the original.
NOT (~) inverts every bit.
Shifts (<<, >>) move bits left or right. Shifting left by one doubles;
shifting right by one halves and discards the remainder.
The practical use most people meet is flags packed into one integer. Eight boolean settings fit in a single byte, each tested with an AND and set with an OR, which is how file permissions, network masks and countless protocol headers are built.
Bytes, kilobytes and the factor-of-1000 argument
Two conventions exist and they disagree by an amount that grows with size.
Decimal (SI): 1 kB = 1,000 bytes, 1 MB = 1,000,000, 1 GB = 1,000,000,000. Used by storage manufacturers and by network speeds.
Binary (IEC): 1 KiB = 1,024 bytes, 1 MiB = 1,048,576, 1 GiB = 1,073,741,824. Used by operating systems reporting memory and, historically, disk space.
The gap is 2.3% at kilobytes and 6.9% at gigabytes. That is why a "1 TB" drive shows as about 931 GB in Windows: the manufacturer sold 10¹² bytes and the operating system is dividing by 1,024 three times.
Neither party is lying. They are using different definitions of the same prefix, and the IEC introduced kibi, mebi and gibi in 1998 specifically to end the ambiguity — with limited success, since almost nobody says "gibibyte".
What this tool assumes
- Input digits must be valid for the base —
2is not a binary digit,Gis not hexadecimal. - Hexadecimal accepts upper or lower case letters and outputs uppercase by convention.
- Numbers are treated as unsigned. Two's complement interpretation depends on a chosen width and is not applied.
- Very large values may exceed the precision of standard number handling; results above 2⁵³ should be treated with caution.
- Everything runs in your browser. Nothing you enter is sent anywhere.