Binary and Hexadecimal Representation

Every value inside a computer — an instruction, a pixel, a character, a memory address — is ultimately a pattern of two-state signals. A CPU has no notion of "the number seven." It only has wires that are high or low voltage. To bridge how humans think about numbers with how hardware stores them, we need something more. We need a positional number system built on exactly two symbols. We also need a compact human-readable shorthand. This shorthand lets us write long strings of those symbols without losing our minds. This article develops binary (base-2) as the native representation of digital hardware. It also covers hexadecimal (base-16), the standard notation programmers use. Hex lets us read and write binary data compactly. The two systems are connected by a beautiful arithmetic coincidence — 16 is a power of 2. This coincidence makes conversion between them almost mechanical. This is unlike conversion between binary and decimal. This is why memory dumps, machine code, IP addresses, color codes, and hash digests are printed in hex rather than decimal. Hex is a lossless, dense stand-in for binary that humans can actually read. The big picture: a positional number system represents a value as a weighted sum of digits. Each digit's weight is a power of the base. Base 10 uses weights …,100,10,1\dots, 100, 10, 1. Base 2 uses weights …,4,2,1\dots, 4, 2, 1. Base 16 uses weights …,256,16,1\dots, 256, 16, 1. Once you can compute in one base, computing in another is easier. It is a matter of changing which weights you use. It also means changing which symbols are legal digits. Everything downstream in computer architecture assumes you are fluent in binary and hex first. This includes Two's Complement Integers for signed values. It includes IEEE 754 Floating Point for reals. It also includes the truth tables of Logic Gates that physically realize arithmetic. By the end of this article you will be able to do several things. You will convert any unsigned integer between decimal, binary, and hexadecimal by hand. You will justify why each conversion algorithm terminates with the correct answer. You will recognize the four-bit-per-hex-digit grouping that makes hex a "binary shorthand." You will also avoid the classic conversion mistakes that trip up beginners.

Key takeaways
  • Positional systems represent a number as ∑di⋅bi\sum d_i \cdot b^i; binary uses base 2 (digits 0,1), hex uses base 16 (digits 0–9, A–F).
  • Decimal-to-binary conversion repeatedly divides by 2 and records remainders; decimal-to-hex does the same with 16.
  • Binary-to-hex conversion is direct: group bits into 4s from the right (since 16=2416 = 2^4), and convert each group to one hex digit.
  • Hex exists purely for human convenience — it is a 4x-denser, lossless alias for binary, not a different hardware representation.
  • Both conversion directions run in O(log⁡N)O(\log N) time in the value NN, since the number of digits scales with log⁡bN\log_b N.
  • These representations describe unsigned magnitudes only; sign and fractions require extensions covered by two's complement and IEEE 754.
The core insight

A number's value is base-independent, but its digit string depends entirely on the base — and because 16=2416=2^4, binary and hexadecimal digit strings translate into each other one hex digit at a time, with no arithmetic required.

The idea

A positional number system represents a nonnegative integer as a sequence of digits dn−1dn−2⋯d1d0d_{n-1} d_{n-2} \cdots d_1 d_0, each drawn from a fixed alphabet of size bb (the base or radix), whose value is defined by

N=∑i=0n−1di⋅bi,0≤di<b.N = \sum_{i=0}^{n-1} d_i \cdot b^i, \qquad 0 \le d_i < b.

Decimal (b=10b=10) uses digits {0,…,9}\{0,\dots,9\}. Binary (b=2b=2) uses only {0,1}\{0,1\}; each digit is called a bit (binary digit), and it maps directly onto a two-state physical device — a transistor that is on or off, a capacitor that is charged or not, a magnetic domain polarized one way or the other. This is precisely why binary, not decimal, is the native language of digital circuits: building a reliable device with ten distinguishable voltage levels is far harder than building one that only needs to distinguish "high" from "low."

Hexadecimal (b=16b=16) uses sixteen digit symbols: 00–99 for the values zero through nine, and the letters AA–FF (or lowercase aa–ff) for the values ten through fifteen, since standard notation has no single symbol for a "digit" above 9. Hexadecimal is never implemented in hardware directly — no wire has sixteen voltage levels. Its entire purpose is human ergonomics: writing out a 32-bit binary number like `10110100111000010100101011110000` is error-prone to read and transcribe, while its hex equivalent `B4E14AF0` is eight characters, easy to chunk, and (as shown below) trivially recoverable back into the exact same bits. The key structural fact tying the two together is that 16=2416 = 2^4. Because each hex digit represents a value from 0 to 15, and every such value has a unique 4-bit binary representation, one hex digit always corresponds to exactly four bits — no more, no less. This "nibble" correspondence is what makes binary–hex conversion a table lookup rather than an arithmetic computation, in sharp contrast to binary–decimal conversion, which genuinely requires division and multiplication because 10 is not a power of 2.

How it works

There are three conversions worth mastering: decimal → binary, decimal → hexadecimal, and binary ↔ hexadecimal. The first two share an algorithm ("repeated division"); the third is a direct regrouping.

Repeated division algorithm. To convert a decimal integer NN into base bb, repeatedly divide NN by bb, recording the remainder at each step; the remainders, read from last to first, form the digit string. ``` ALGORITHM ConvertToBase(N, b) // N: nonnegative decimal integer, b: target base (b >= 2) if N == 0: return "0" // base case: zero is its own representation digits = empty list while N > 0: r = N mod b // remainder is the next least-significant digit digits.append(DigitSymbol(r)) // map 0-9 -> '0'-'9', 10-15 -> 'A'-'F' for hex N = N div b // integer division: strip off that digit's weight reverse(digits) // remainders came out least-significant first return concatenate(digits) ```

Why the remainders are the digits. Write NN in base bb as N=dn−1bn−1+⋯+d1b+d0N = d_{n-1}b^{n-1} + \cdots + d_1 b + d_0. Then

N  b=d0,⌊N/b⌋=dn−1bn−2+⋯+d1,N \bmod b = d_0, \qquad \lfloor N / b \rfloor = d_{n-1}b^{n-2} + \cdots + d_1,

because every term except d0d_0 is a multiple of bb. So dividing by bb once "peels off" d0d_0 as the remainder and leaves exactly the number formed by the remaining, higher-order digits — the same problem, one digit smaller. Repeating this until the quotient reaches 0 recovers every digit, from least significant to most significant, which is why the collected remainders must be reversed at the end.

Binary ↔ hexadecimal via nibble grouping. Because 161=2416^1 = 2^4, each hex digit is worth exactly 4 bits. To go binary → hex, pad the bit string on the left with zeros until its length is a multiple of 4, split it into groups of 4 starting from the right, and translate each group independently using the 4-bit table below. To go hex → binary, reverse the process: replace each hex digit with its fixed 4-bit pattern and concatenate. ``` ALGORITHM BinaryToHex(bits) // bits: string of '0'/'1', most significant bit first pad_len = (4 - length(bits) mod 4) mod 4 bits = "0" * pad_len + bits // left-pad to a multiple of 4 groups = split bits into chunks of 4, left to right result = "" for each group g in groups: value = binary_value(g) // 0..15, computed from the 4 bits directly result = result + HexSymbol(value) return result ALGORITHM HexToBinary(hexstr) result = "" for each character c in hexstr: v = HexDigitValue(c) // '0'-'9','A'-'F' -> 0..15 result = result + FourBitBinary(v) // fixed 4-bit pattern, no arithmetic return result ``` The 4-bit lookup table used by both directions: ``` Hex Binary Decimal 0 0000 0 1 0001 1 2 0010 2 3 0011 3 4 0100 4 5 0101 5 6 0110 6 7 0111 7 8 1000 8 9 1001 9 A 1010 10 B 1011 11 C 1100 12 D 1101 13 E 1110 14 F 1111 15 ```

Why grouping works, precisely. Split an nn-bit binary string (padded to a multiple of 4) into groups Gk−1,…,G1,G0G_{k-1}, \dots, G_1, G_0 of 4 bits each, so bit position ii within the whole number lives in group ⌊i/4⌋\lfloor i/4 \rfloor at local position i  4i \bmod 4. The value of the whole number is

N=∑i=0n−1bi⋅2i=∑j=0k−124j∑l=03b4j+l 2l=∑j=0k−1vj⋅16j,N = \sum_{i=0}^{n-1} b_i \cdot 2^i = \sum_{j=0}^{k-1} 2^{4j} \sum_{l=0}^{3} b_{4j+l}\, 2^{l} = \sum_{j=0}^{k-1} v_j \cdot 16^{j},

where vj=∑lb4j+l2lv_j = \sum_l b_{4j+l}2^l is exactly the 4-bit value of group GjG_j, and 16j=(24)j16^j = (2^4)^j. This is precisely the definition of NN written in base 16 with digits vjv_j. So grouping bits by 4 and converting each group independently is not an approximation or a shortcut with edge cases — it is a direct algebraic identity, which is why no carrying or borrowing across groups is ever needed.

Figure
A 16-bit binary value grouped into four hex digits, and the place-value structure of positional notation
16-bit value: 1011 0100 1110 0001₂ = B4E1₁₆ 1 0 1 1 0 1 0 0 1 1 1 0 0 0 0 1 B 4 E 1 each 4-bit nibble maps to exactly one hex digit — no carrying between groups Place values (base 16): 16³=4096 16²=256 16¹=16 16⁰=1 digit B=11 → 11×4096 = 45056 digit 4 → 4×256 = 1024 digit E=14 → 14×16 = 224 digit 1 → 1×1 = 1 total = 45056+1024+224+1 = 46305

Complexity & correctness

Time complexity of repeated division. Converting a value NN to base bb via `ConvertToBase` performs one division per output digit, and a number NN has ⌈log⁡b(N+1)⌉\lceil \log_b(N+1) \rceil digits in base bb. Each division/modulo step on fixed-width machine integers is O(1)O(1) under the standard RAM model (or O(log⁡N)O(\log N) bit-level cost if you insist on counting bit operations for arbitrary-precision integers). So the total work is

O(log⁡bN)O(\log_b N)

divisions, i.e., logarithmic in the value being converted, linear in the number of digits produced. This is optimal: you cannot emit fewer than Θ(log⁡bN)\Theta(\log_b N) symbols and still uniquely represent NN, by a simple counting argument — bkb^k distinct digit strings of length kk can represent at most bkb^k distinct values, so representing values up to NN requires k≥log⁡b(N+1)k \ge \log_b(N+1).

Time complexity of binary–hex grouping. This algorithm makes a single linear pass over the bit string (or hex string), doing O(1)O(1) table-lookup work per group/digit. With nn bits (equivalently n/4n/4 hex digits), the cost is

O(n)=O(log⁡2N),O(n) = O(\log_2 N),

again linear in the input/output length and asymptotically identical to repeated division — but with a much smaller constant factor and no actual arithmetic, since it is pure regrouping.

Space complexity. Both algorithms use O(log⁡bN)O(\log_b N) space to store the output digit string (and O(1)O(1) auxiliary space beyond that, aside from the final reversal in `ConvertToBase`, which can be done in place).

Correctness argument for repeated division (invariant + induction). Claim: at the start of each loop iteration, the current value of `N` equals ∑idibi−k\sum_{i} d_i b^{i - k} where kk is the number of digits already extracted and did_i are the true base-bb digits of the original input. Base case: before any iterations, k=0k=0 and the claim holds trivially (`N` is the original input). Inductive step: given the invariant holds with remaining value N′=∑i≥kdibi−kN' = \sum_{i\ge k} d_i b^{i-k}, we showed above that N′  b=dkN' \bmod b = d_k and ⌊N′/b⌋=∑i≥k+1dibi−k−1\lfloor N'/b \rfloor = \sum_{i \ge k+1} d_i b^{i-k-1}, which is exactly the invariant with kk incremented by 1. The loop terminates because `N` strictly decreases (it is divided by b≥2b \ge 2 each time) and is a nonnegative integer, so it must reach 0 in finitely many (⌈log⁡b(N+1)⌉\lceil\log_b(N+1)\rceil) steps. On termination, every digit has been extracted in order d0,d1,…,dn−1d_0, d_1, \dots, d_{n-1}; reversing recovers the standard most-significant-first order. ■\blacksquare

Correctness argument for nibble grouping. This was already given algebraically above: splitting an nn-bit string into 4-bit groups and reading each as a value vj∈[0,15]v_j \in [0,15] satisfies N=∑jvj⋅16jN = \sum_j v_j \cdot 16^j by definition of positional notation in base 16, since 16=2416 = 2^4. No induction is even needed — it is a direct rewriting of one summation as a nested summation, exploiting that 4 divides evenly into the exponent structure. This is also precisely why grouping by 3 bits works for octal (base 8 = 232^3), and why no such clean grouping exists for decimal, since 1010 is not a power of 22.

Honest tradeoffs. Repeated division is universal — it works for converting to any base, including ones with no power-of-two relationship to binary (like decimal) — but it requires genuine arithmetic (division, modulo) at every step. Nibble grouping is essentially free computationally (bit shifts and masks in hardware, or table lookups by hand) but only works because 1616 is a power of 22; you cannot use this shortcut to go directly from binary to decimal.

Worked example

Convert N=46305N = 46305 (decimal) to binary and to hexadecimal, then cross-check the two answers against each other via nibble grouping.

Step 1 — decimal to binary via repeated division by 2:

NNN  2N \bmod 2 (bit, LSB first)N÷2N \div 2
46305123152
23152011576
1157605788
578802894
289401447
14471723
7231361
3611180
180090
90045
45122
22011
1115
512
201
110

Reading the bit column from bottom to top gives:

4630510=1011 0100 1110 0001246305_{10} = 1011\,0100\,1110\,0001_2

Step 2 — decimal to hexadecimal via repeated division by 16:

NNN  16N \bmod 16hex digitN÷16N \div 16
46305112894
289414E180
1804411
1111B0

Reading bottom to top: 4630510=B4E11646305_{10} = \text{B4E1}_{16}.

Step 3 — cross-check via nibble grouping (no arithmetic). Take the binary answer from Step 1 and split into 4-bit groups from the right: ``` 1011 0100 1110 0001 B 4 E 1 ``` This matches Step 2's independently-computed answer exactly, B4E116\text{B4E1}_{16} — confirming both algorithms agree, as the correctness proofs guarantee they must, since they are two different routes computing the same base-16 representation of the same number.

Step 4 — verify by expansion.

B4E116=11⋅163+4⋅162+14⋅161+1⋅160=45056+1024+224+1=46305.\text{B4E1}_{16} = 11\cdot16^3 + 4\cdot16^2 + 14\cdot16^1 + 1\cdot16^0 = 45056 + 1024 + 224 + 1 = 46305.

The arithmetic closes the loop back to the original decimal value, confirming correctness end to end.

Common pitfalls & traps

PitfallWhy it happensFix
Forgetting to reverse remaindersRepeated division naturally produces digits least-significant-firstAlways reverse the collected remainder list before reading it as the final digit string
Padding to the wrong nibble boundaryBinary strings from real programs may not have length divisible by 4Left-pad with zeros to the next multiple of 4 before grouping, never right-pad
Mixing up hex letter case or valueConfusing A=10 with A=1, or writing digit 10 as "10" instead of "A"Memorize the 0–15 to 0–F table cold; a hex digit is always exactly one character
Treating base conversion as changing the valueLearners sometimes think 1012≠10110101_2 \ne 101_{10} is "wrong" or a "different number"The value is base-independent; only the digit string changes — always keep a subscript on ambiguous numerals
Assuming hex-to-decimal needs bit groupingGrouping only helps between binary and a power-of-2 base like hex/octalConvert hex to decimal via ∑di⋅16i\sum d_i \cdot 16^i, or route through binary as an intermediate if that's easier

Pioneers & history

The binary numeral system predates electronic computing by centuries as a mathematical curiosity, but its marriage to physical hardware — and the parallel invention of compact notations like hexadecimal to manage it — is a distinctly 20th-century computer-architecture story.

Origins · 1 minGottfried Wilhelm Leibniz and how this was discovered
GW
Gottfried Wilhelm Leibniz
1646–1716 · Developed and formalized the binary numeral system, including arithmetic rules for it, though he had no electronic hardware to apply it to

Leibniz saw binary as a philosophical and mathematical elegance — a system built from the "nothing" and the "something" — publishing his systematic treatment of binary arithmetic in 1703, roughly two and a half centuries before it became the working substrate of digital computers.

CS
Claude Shannon
1916–2001 · Showed that Boolean algebra could describe switching circuits, providing the theoretical bridge between binary logic and physical electrical networks

Shannon's 1937 master's thesis proved that networks of relays and switches could implement any Boolean function, meaning binary logic was not just a numbering convention but the literal design language of digital hardware — a result some consider the most consequential master's thesis of the century.

History
1703
— Leibniz publishes a formal treatise establishing binary arithmetic.
1937
— Claude Shannon connects Boolean algebra to switching circuit design.
1945
— John von Neumann's stored-program architecture report assumes binary storage of instructions and data.
1963
— ASCII standardizes character encoding as fixed-width binary patterns, cementing byte-oriented, hex-describable data.
1970
s — Hexadecimal notation becomes the de facto standard for reading memory dumps and machine code on byte-addressed, nibble-aligned architectures.

Where this matters

Every layer of a real computing system leans on the binary/hex duality developed here. Machine instructions are stored as raw binary in memory, but assemblers, disassemblers, and debuggers print them in hex because a 32-bit instruction word is a readable 8-character hex string instead of an error-prone 32-character bit string. Memory addresses in a debugger (`0x7ffee23a1050`), color values in CSS and image formats (`#FF5733`), MAC addresses, cryptographic hash digests (SHA-256 output shown as 64 hex characters), and IPv6 addresses are all hex encodings of underlying binary data, chosen specifically because of the clean 4-bit-per-hex-digit correspondence proven above. Inside the CPU itself, every arithmetic and logic unit operates purely on bits — there is no hexadecimal circuitry anywhere in silicon. Understanding binary representation is the prerequisite for Logic Gates, which physically implement addition, comparison, and Boolean operations on those bit patterns. It is also the prerequisite for Two's Complement Integers, which extend unsigned binary to represent negative numbers using the same bit patterns reinterpreted, and for IEEE 754 Floating Point, which further subdivides a binary word into sign, exponent, and mantissa fields to represent real numbers. Compilers, linkers, and file-format specifications (ELF headers, PNG chunks, network packet headers) are routinely documented in hex precisely because it is the most legible lossless view onto the binary reality that hardware actually manipulates.

Ask the tutor: How does the same bit pattern get reinterpreted to represent negative numbers under two's complement?

Why do computers use binary instead of decimal internally?

Binary maps naturally onto physical two-state devices: a transistor that is on or off, a voltage that is high or low, a magnetic domain polarized one way or another. Reliably building and distinguishing ten different voltage levels (for decimal) inside a chip is far harder and noisier than distinguishing just two. Claude Shannon's 1937 thesis formalized this by showing Boolean algebra — a two-valued logic — exactly describes switching circuit behavior, making binary the natural mathematical language for digital hardware. Every other base used in computing, including hexadecimal and octal, is layered on top as a notation for humans, not a change to the underlying physical representation.

Is hexadecimal ever actually stored or computed on directly in hardware?

No. Hardware registers, buses, and memory cells are entirely binary; there is no physical hex ALU or hex memory cell. Hexadecimal is purely a display and input convention used by humans (and by tools like debuggers, disassemblers, and hex editors) because it compresses long bit strings into a shorter, easier-to-read form. When you type a hex literal like `0x2A` into source code, the compiler converts it to the equivalent binary pattern before it ever reaches the hardware; hex never "exists" as a distinct electrical representation.

How many bits does one hexadecimal digit always represent?

Exactly 4 bits, no more and no less, because 16=2416 = 2^4 and every value from 0 to 15 has a unique 4-bit binary pattern. This is a fixed, universal correspondence — not an approximation — which is why converting between binary and hex never requires carrying, borrowing, or any arithmetic: you simply split the bit string into groups of 4 and translate each group using a fixed lookup table, or run the process in reverse.

Why can't you use the same grouping trick to convert binary directly to decimal?

The grouping trick relies on the target base being a power of 2 — specifically, that some fixed number of bits (4, for hex; 3, for octal) exactly represents one digit of the target base with no leftover fractional bit-weight. Decimal's base, 10, is not a power of 2, so there is no fixed number of bits that cleanly corresponds to "one decimal digit"; the place values 1,10,100,…1, 10, 100, \dots simply do not line up with binary place values 1,2,4,8,…1, 2, 4, 8, \dots. Converting binary to decimal therefore requires genuine positional-value arithmetic (multiplying each bit by its power of 2 and summing), not regrouping.

What is the difference between an unsigned representation and the signed representations covered later?

Everything in this article describes unsigned integers: every bit pattern represents a nonnegative magnitude, and an nn-bit pattern can represent values from 00 to 2n−12^n - 1. Real programs also need negative numbers, which requires reserving part of the representation's information to encode sign — the dominant scheme in modern hardware is Two's Complement Integers, which reinterprets the same bit patterns so that roughly half represent negative values, while keeping addition circuitry identical to the unsigned case. Binary and hex notation themselves do not change; only how a fixed-width bit pattern is interpreted changes.

Why is hexadecimal specifically preferred over, say, octal (base 8) in modern practice?

Octal (base 8 = 232^3) has the same clean regrouping property as hex, using 3-bit groups, and was popular on early machines with word sizes divisible by 3 (like 12-bit or 36-bit systems). Modern hardware standardized on byte-oriented, power-of-two word sizes (8, 16, 32, 64 bits), all of which divide evenly by 4 but not always as neatly by 3 in a way that aligns with byte boundaries — a byte is exactly two hex digits, but not a whole number of octal digits. Hexadecimal's alignment with the ubiquitous 8-bit byte made it the practical winner.

What happens if I try to convert a number to a base greater than 16, like base 36?

The repeated-division algorithm generalizes without change — divide by b=36b=36, take remainders, map values 0–35 to digit symbols (conventionally 0–9 then A–Z), and reverse at the end. Bases up to 36 are common in applications like URL shorteners and compact ID encodings because they use the full alphanumeric alphabet. The grouping shortcut, however, only works when converting to or from a base that is itself a power of 2 (like hex or octal) relative to binary; base 36 is not a power of 2, so no bit-grouping shortcut exists there, and repeated division is the only correct method.

Where this leads