Published
Two different things get called "base conversion". One is writing the same number in a different notation, which is arithmetic. The other is representing arbitrary bytes as printable characters, which is encoding. Hexadecimal sits in both worlds and that is why the two get confused. This guide separates them, works through the conversions by hand, and covers the signed-integer representation that makes 0xFF sometimes mean 255 and sometimes mean -1.
Positional notation is the only idea here
A number written in base b is a sum of digits multiplied by powers of b, counting positions from zero at the right. The decimal string 2026 means 2 times 1000 plus 0 times 100 plus 2 times 10 plus 6. Nothing about that procedure is specific to ten; replace the base and the same rule produces every other notation.
The important consequence is that the base is a property of the writing, not of the number. The quantity 2026 is the same quantity whether it is written 2026, 11111101010, 3752 or 7EA. A conversion changes how it looks and never changes what it is, which is why a conversion can always be verified by converting back.
Two directions of manual conversion are worth knowing. To go from decimal to base b, divide repeatedly by b and read the remainders bottom-up. To go from base b to decimal, multiply each digit by its place value and add. Both are short enough to do on paper for small values, and doing one by hand is the fastest way to stop being uncertain about what a converter is telling you.
The digits themselves need names beyond nine, which is where letters come in. Hexadecimal borrows A through F for the values 10 through 15. This is purely a naming convention: A is not a letter in this context, it is the digit worth ten.
Decimal to binary, by repeated division by 2 (read remainders upward):
2026 / 2 = 1013 r 0 1013 / 2 = 506 r 1 506 / 2 = 253 r 0
253 / 2 = 126 r 1 126 / 2 = 63 r 0 63 / 2 = 31 r 1
31 / 2 = 15 r 1 15 / 2 = 7 r 1 7 / 2 = 3 r 1
3 / 2 = 1 r 1 1 / 2 = 0 r 1
-> 11111101010
Check by expanding back to decimal:
1024 + 512 + 256 + 128 + 64 + 32 + 8 + 2 = 2026
The same value in every common base:
decimal 2026
binary 11111101010
octal 3752 (3*512 + 7*64 + 5*8 + 2)
hexadecimal 7EA (7*256 + 14*16 + 10)Binary, octal, hexadecimal, and why hex won
Binary is what the hardware stores, and it is unusable for humans past about a byte: 11111101010 takes real effort to read and even more to say out loud. Octal and hexadecimal exist as shorthand for binary, and they work because their bases are exact powers of two. One octal digit is exactly three bits and one hex digit is exactly four, so converting between them and binary needs no arithmetic at all — only regrouping.
Hexadecimal won because four bits divide a byte evenly and three do not. Two hex digits are one byte, always, with no carry across the boundary; eight hex digits are one 32-bit word. Octal needs three digits to cover eight bits and the third digit only uses two of its three bits, so octal representations of byte data have a ragged high digit. On the 12-bit and 36-bit machines where octal became popular this was not a problem; on 8-bit bytes it is.
Octal did not disappear, though. Unix file permissions are still written in octal for exactly the reason octal is otherwise awkward: permission bits genuinely come in groups of three. 755 is rwx for the owner, r-x for the group and r-x for everyone else, and each octal digit maps to one triple. The umask and the chmod arguments follow the same logic.
One historical hazard survives into modern code. In C, and in the languages that copied its literal syntax, a leading zero marks an octal literal, so 011 is the number nine and not eleven. This is why modern languages introduced the explicit 0o prefix and why several of them now reject a bare leading zero outright.
Bits per digit is exact only for bases that are powers of two, which is why those three dominate systems programming.
| Base | Digit alphabet | Bits per digit | Common prefix | Where you meet it |
|---|---|---|---|---|
| 2 (binary) | 0 1 | 1 | 0b | Bitmasks, flags, subnet masks, hardware registers |
| 8 (octal) | 0-7 | 3 | 0o (or a bare leading 0 in C) | Unix file permissions, umask, some escape sequences |
| 10 (decimal) | 0-9 | about 3.32 | none | Everything a human types |
| 16 (hexadecimal) | 0-9 A-F | 4 | 0x, # in CSS, U+ for code points | Memory addresses, byte dumps, colours, hashes, UUIDs |
| 36 | 0-9 A-Z | about 5.17 | none | Compact numeric IDs in URLs and short links |
Two's complement, or why 0xFF is sometimes -1
Numeric bases as described so far have no way to write a negative value; the minus sign is outside the notation. Hardware cannot use a minus sign, so signed integers are represented with a fixed-width convention, and the convention every modern machine uses is two's complement.
The rule is mechanical: to represent a negative value in n bits, take the positive value, invert every bit, and add one. For -42 in eight bits, 42 is 00101010, inverting gives 11010101, adding one gives 11010110, which is 0xD6. The top bit acts as a sign indicator — set means negative — but it is not a separate sign field, which is why the arithmetic works: the processor adds and subtracts signed and unsigned values with the same circuit.
The consequence for reading hex dumps is that the same bit pattern has two readings and only the declared type decides between them. 0xFF is 255 as an unsigned byte and -1 as a signed one. 0x80 is 128 unsigned and -128 signed, and it is the one value in the range whose negation does not fit, which is why abs() on the minimum integer is undefined behaviour in C and returns the same negative number in several other languages. A debugger showing you raw bytes does not know the type, so it usually shows the unsigned reading.
Two's complement also explains sign extension. Widening a signed 8-bit -1 to 32 bits produces 0xFFFFFFFF, not 0x000000FF, because the sign bit is copied into every new position. Widening an unsigned 0xFF produces 0x000000FF. A protocol that does not state the signedness of a field is a protocol that will eventually be parsed both ways.
bits unsigned signed (two's complement)
00101010 42 42
10110101 181 -75 (181 - 256)
11010110 214 -42 (214 - 256)
11111111 255 -1
10000000 128 -128 negating this one overflows
Deriving -42 in eight bits:
42 = 00101010
invert every bit = 11010101
add one = 11010110 = 0xD6
Sign extension to 32 bits:
signed -1 (0xFF) -> 0xFFFFFFFF
unsigned 255 (0xFF) -> 0x000000FFRFC 4648 encodes bytes, not numbers
Base16, Base32 and Base64 are specified together in RFC 4648, and despite the names they are not doing the same job as the numeric bases above. They do not convert a number into another notation. They take a sequence of bytes and re-express it as printable ASCII, so it can pass through a system that would otherwise mangle it — an email body, a URL, a JSON string, a header value.
The mechanism is regrouping of the bit stream. Base16 takes four bits at a time and emits one of sixteen characters, doubling the size. Base32 takes five bits at a time and emits one of thirty-two, growing the data by 60 percent. Base64 takes six bits at a time and emits one of sixty-four, growing it by 33 percent. Because bytes are eight bits, only Base64 on a multiple of three bytes and Base32 on a multiple of five bytes come out even; the rest need padding, which is what the trailing equals signs are.
Two details cause most of the confusion. First, leading zero bytes are preserved exactly, because each group of bits is encoded independently — unlike a numeric conversion, where leading zeros vanish. Second, the standard alphabet's plus and slash are unsafe in a URL, so RFC 4648 defines a URL-safe variant that substitutes minus and underscore and usually drops the padding. JWTs, WebAuthn and most modern token formats use that variant, which is why a token pasted into a standard Base64 decoder often fails.
Base32 is worth knowing about even though it is bulkier. Its alphabet is uppercase letters and the digits two to seven, chosen so that the visually confusable characters zero, one and eight are absent. That makes it the right choice for anything a person will read aloud, type from a screen or write on paper: TOTP secrets, onion addresses and DNS-encoded payloads all use it.
| Encoding | Alphabet | Bits per character | Size growth | "Man" becomes |
|---|---|---|---|---|
| Base16 (hex) | 0-9 A-F | 4 | 100 percent | 4D616E |
| Base32 | A-Z 2-7 | 5 | 60 percent | JVQW4=== |
| Base32hex | 0-9 A-V | 5 | 60 percent | 9LGMS=== |
| Base64 | A-Z a-z 0-9 + / | 6 | 33 percent | TWFu |
| Base64url | A-Z a-z 0-9 - _ | 6 | 33 percent | TWFu |
| Base58 | Base62 minus 0 O I l | about 5.86 | about 37 percent | SzVj |
"Man" -> bytes 4D 61 6E
01001101 01100001 01101110 three bytes, 24 bits
010011 010110 000101 101110 regrouped into four 6-bit values
19 22 5 46 decimal values
T W F u index into A-Z a-z 0-9 + /
Padding appears when the input is not a multiple of three bytes:
"Man" (3 bytes) -> TWFu
"Ma" (2 bytes) -> TWE=
"M" (1 byte) -> TQ==
Size in practice, for 100 bytes of input:
Base64 136 characters Base32 160 characters Base16 200 charactersBase58, and encodings built for human eyes
Base58 is the odd one out. It is not in RFC 4648, it is not a power of two, and it does not regroup the bit stream. It treats the whole byte sequence as one very large integer and converts that integer to base 58, which makes it a genuine numeric conversion applied to binary data.
The alphabet is the sixty-two alphanumerics with four characters removed: the digit zero, uppercase O, uppercase I and lowercase l. Those four are the ones that collapse into each other in most fonts, and removing them is the entire point. Base58 exists to be copied by hand, read over a phone and printed on paper without transcription errors, which is why Bitcoin addresses, IPFS CIDv0 identifiers and Solana public keys all use it.
Because it is arithmetic rather than regrouping, Base58 has two properties the RFC 4648 encodings do not. Leading zero bytes would disappear in the conversion, so the specification adds them back explicitly as leading 1 characters, one per zero byte. And there is no way to encode or decode a stream in fixed-size chunks: the whole value must be processed together, which makes it quadratic in the input length and unsuitable for large payloads. Nobody Base58-encodes a file.
In practice Base58 usually appears as Base58Check, which appends a four-byte truncated double-SHA-256 checksum before encoding. That is what lets a wallet reject a mistyped address instead of sending funds into nothing, and it is a reminder that the encoding itself provides no integrity guarantee — the checksum is a separate layer bolted on top.
Where each base actually turns up
Recognising a base on sight saves a lot of time. A string of exactly 32, 40 or 64 characters from 0-9a-f is a hash digest: MD5, SHA-1 and SHA-256 respectively. A string ending in one or two equals signs is Base64. An uppercase string with no zero, one or eight and a length that is a multiple of eight is Base32. A mixed-case alphanumeric string with no zero and no letter l is very likely Base58.
Hexadecimal dominates anywhere a byte boundary matters: memory addresses, MAC addresses, IPv6 groups, UUIDs, CSS colours, Unicode code points written as U+1F600, and every hex dump ever produced. Binary appears where individual bits carry meaning: permission and capability flags, subnet masks, hardware registers, feature bitfields. Decimal is for quantities people talk about. Base64 and its relatives are for moving bytes through text channels, never for compression and never for secrecy.
When you need to move between these, the Number Base Converter handles the numeric bases and the Base64 tool handles the byte encodings. The distinction between the two tools is the distinction this guide is built around: one is converting a quantity, the other is repackaging a byte sequence. Asking the wrong one is how people end up trying to decode a colour code.
- Two hex digits are one byte, always — use that to sanity-check any hex string's length against the data it should contain.
- A bare leading zero means octal in C-derived languages; write 0o explicitly or drop the zero.
- Leading zeros survive Base16, Base32 and Base64 but not a numeric conversion, which is why 0x0042 and 0x42 are the same number and different byte strings.
- If a token fails in a Base64 decoder, try the URL-safe alphabet before assuming the token is corrupt.
- Signedness is not stored in the bits; a hex dump alone cannot tell you whether 0xFF is 255 or -1.
What to remember
- A number's base is a property of how it is written, never of the number, so every conversion can be checked by converting back.
- Hexadecimal is the default systems notation because one digit is exactly four bits and two digits are exactly one byte.
- Base16, Base32 and Base64 regroup a byte stream into printable characters and are not numeric conversions, which is why they preserve leading zero bytes and need padding.
- Base58 is a real numeric conversion of the whole byte string, drops the four confusable characters, and cannot be streamed.
- Two's complement means a bit pattern has no inherent sign: 0xFF is 255 or -1 depending entirely on the declared type.