Number Systems

Digital Logic 101 · 18 min read

dn-1bn-1......d2b2d1b1d0b0digits (top) · place-weights (bottom)
Figure 1. Positional notation. Each digit slot carries a power of the base; the value of the number is the sum of digit times place-weight.

Every number system in digital electronics is positional: the digit's value depends on which slot it sits in. The value of a digit string dn1dn2d1d0d_{n-1} d_{n-2} \dots d_1 d_0 in base bb is:

N=i=0n1dibiN = \sum_{i=0}^{n-1} d_i \cdot b^{i}

Change the base and you change the place-weights. The bases below all describe the same physical bits — they just slice them differently for human eyes.

Binary (base 2)

112806413201618140211128 + 32 + 8 + 4 + 1 = 173
Figure 2. The 8-bit value 10101101₂. Each cell is one bit; weights double every step from the LSB on the right.

Binary uses two symbols, 00 and 11, mapped to the two voltage levels of a digital wire. A single binary digit is a bit; four bits make a nibble; eight bits make a byte.

The 8-bit pattern in Figure 2 evaluates to 128+32+8+4+1=173128 + 32 + 8 + 4 + 1 = 173. An nn-bit unsigned register holds values from 00 to 2n12^{n} - 1.

Hexadecimal (base 16)

1010A1101D
Figure 3. Hex packs four binary bits into one digit. The same byte 10101101₂ regroups to AD₁₆.

Hex uses sixteen symbols: 0099 then AAFF for ten through fifteen. One hex digit covers exactly four binary bits, so a byte is always two hex digits.

Hex is preferred for memory addresses, register dumps, and machine code because FFFF16=6553510FFFF_{16} = 65535_{10} is faster to read and write than the sixteen-character binary string. The 0x0x prefix marks a hex literal in C, JavaScript, and most assemblers.

Octal (base 8)

010210151015
Figure 4. Octal packs three binary bits per digit. The same byte 10101101₂ regroups to 255₈ (with an implicit leading zero in the top group).

Octal uses 0077 and packs three bits per digit. It mostly survives in Unix file-permission bits (chmod 755chmod\ 755) and a few legacy systems, where 3-bit groupings are convenient.

Converting between bases

Two methods cover every integer conversion. Weighted sum is best when you're reading a value out of a fixed-base representation; it just adds up dibid_i \cdot b^{i}. Repeated division is best when you're going into a base; it strips off one digit at a time from the LSB.

Decimal → Binary: repeated division by 2

173 ÷ 2StepDividendQuotientRemainder117386128643034321142110151050652172108101read up173₁₀ = 101011012
Figure 5. Convert 173₁₀ to binary by repeatedly dividing by 2. The remainder column, read bottom-up, is the binary value: 10101101₂. Each row peels off one bit, starting from the LSB.

The same algorithm works for any target base. To convert 17310173_{10} to octal, divide by 88 instead: remainders 5,5,25, 5, 2 read bottom-up give 2558255_{8}. To convert to hex, divide by 1616: remainders 13,1013, 10 read bottom-up give AD16AD_{16}.

Binary → Decimal: weighted sum

112806413201618140211128 + 32 + 8 + 4 + 1 = 173sum the weights of every '1' bit
Figure 6. Convert 10101101₂ to decimal by summing the weights of every '1' bit. Same operation as Figure 2, written out as a column.

Weighted sum also works directly from hex or octal — the only thing that changes is the base in the place-weights. AD16=1016+13=17310AD_{16} = 10 \cdot 16 + 13 = 173_{10}; 2558=264+58+5=17310255_{8} = 2 \cdot 64 + 5 \cdot 8 + 5 = 173_{10}. Same value, three encodings.

Binary ↔ Octal / Hex: bit grouping

Because 8=238 = 2^{3} and 16=2416 = 2^{4}, there's a shortcut: group binary bits into 3s for octal or 4s for hex, starting at the radix point. Each group is a single octal/hex digit. Figures 3 and 4 above show the grouping.

Weighted-sum reads a value out of a representation; it sums dibid_i \cdot b^{i}. Repeated-division writes a value into a representation; remainders bottom-up are the digits. Between binary and octal/hex, bit-grouping beats both — it's a re-slicing of the same bits.

Fractional numbers

Positional notation extends to the right of the radix point with negative powers: d1b1+d2b2+d_{-1} \cdot b^{-1} + d_{-2} \cdot b^{-2} + \dots. So the binary fraction 0.10120.101_2 means 12+18=0.62510\tfrac{1}{2} + \tfrac{1}{8} = 0.625_{10}.

Decimal fraction → Binary: repeated multiplication

0.625 × 2StepFraction× 2BitRemainder10.6251.2510.2520.250.500.530.5110read down0.625₁₀ = 0.1012 (terminates)
Figure 7. Convert 0.625₁₀ to binary. Multiply by 2; the integer part of the product is the next bit (top-down — the MSB after the radix point comes first). Stop when the fractional part hits 0.

Integer and fractional parts convert independently. To encode 173.62510173.625_{10} in binary: integer part 17310=101011012173_{10} = 10101101_2 by repeated division; fractional part 0.62510=0.10120.625_{10} = 0.101_2 by repeated multiplication; concatenate with the radix point: 10101101.101210101101.101_2.

BCD (Binary-Coded Decimal)

100017011130011codes 1010..1111 inside each nibble are illegal
Figure 8. BCD encodes each decimal digit independently in 4 bits. 173₁₀ becomes the three nibbles 0001 0111 0011 — six codes per nibble (1010..1111) are wasted.

BCD treats decimal as the source of truth and stores each digit on its own. It's wasteful — six of every sixteen 4-bit codes are illegal — but the human-readable digits map directly to 7-segment displays and BCD adders.

Note the difference: 17310=101011012173_{10} = 10101101_{2} as straight binary, but in BCD the same value is 000101110011BCD0001\,0111\,0011_{\text{BCD}}. Same number, different encoding.

Gray code

nGrayBinary00000001001001201101030100114110100511110161011107100111
Figure 9. Gray code orders the integers 0..7 so adjacent rows differ by exactly one bit. Compare to the right column where binary 011 → 100 flips three bits at once.

Gray code is a binary encoding where consecutive integers differ by a single bit. The standard formula is g=n(n1)g = n \oplus (n \gg 1) — XOR a number with itself shifted right one place.

The single-bit-change property eliminates transient mis-reads in mechanical rotary encoders and is exactly the row/column ordering used inside Karnaugh maps so that adjacent cells differ by one variable.

Two's complement (signed binary)

00000000110010200113010040101501106011171000-81001-71010-61011-51100-41101-31110-21111-1MSB = 0 → non-negative · MSB = 1 → negative
Figure 10. Number line for 4-bit two's complement: the 16 patterns 0000..1111 split into 0..7 and −8..−1. The MSB is the sign bit — 0 means non-negative, 1 means negative.

Two's complement is the standard way digital hardware represents negative integers. The same bit pattern is read as unsigned (range 00 to 2n12^{n} - 1) or as signed (range 2n1-2^{n-1} to 2n112^{n-1} - 1) — it's the same bits with a different interpretation of the MSB.

To negate a value: invert every bit, then add 1. +310=00112+3_{10} = 0011_2 negates to 0011+1=1100+1=11012=310\sim 0011 + 1 = 1100 + 1 = 1101_2 = -3_{10}. The same algorithm round-trips: applying it again to 110121101_2 recovers 00112=+30011_2 = +3.

Range table: a 4-bit signed register holds 8-8..+7+7; 8-bit holds 128-128..+127+127; 16-bit holds 32,768-32{,}768..+32,767+32{,}767; 32-bit holds 231-2^{31}..23112^{31} - 1. The range is asymmetric — there is one more negative value than positive value.

Sign extension

To widen a two's-complement value from nn bits to mm bits, copy the sign bit (MSB) into every new high bit. So 110121101_2 (4-bit 3-3) widens to 11111101211111101_2 (8-bit 3-3); the original MSB 11 filled the four new high positions. Zero-padding the high bits instead would have given 000011012=+1300001101_2 = +13, which is wrong. Zero-extension only works for unsigned values.

Two's complement is what every ALU's adder assumes. The hardware computes the same XOR/AND/OR sum in both signed and unsigned modes — what changes is just which flag (carry-out for unsigned, overflow for signed) tells you the result is correct. See Adders & Subtractors later in this subject for the gate-level view.

Conversion reference (0..15)

DecimalBinaryHexOctalGray
00000000000
10001110001
20010220011
30011330010
40100440110
50101550111
60110660101
70111770100
810008101100
910019111101
101010A121111
111011B131110
121100C141010
131101D151011
141110E161001
151111F171000
Figure 11. The same sixteen integers across all five common encodings.

Read more on this