Skip to content

1.3 — Binary & Number Systems

We've said "an n-bit number" on faith for two chapters. The adder in 1.2 added them; the register held them. But how does a row of on/off switches actually mean 0, or 42, or −7? That's a choice — an encoding — and the choices humans made here are quietly brilliant. This chapter makes "number" precise: how positional notation works in any base, why hexadecimal is every engineer's shorthand, the trick that lets the same adder subtract (two's complement), what happens when a number outgrows its box (overflow), and the byte/nibble/kilobyte vocabulary you'll read for the rest of your life.

1. Positional notation — the idea you already own

You already count in base ten, and you already understand place value — you just never had to say it out loud. The number 4,072 means:

4\times10^3 + 0\times10^2 + 7\times10^1 + 2\times10^0

Each position is worth ten times the one to its right, because we have ten digit-symbols (0–9). That "ten" is the base (or radix). Nothing about the universe requires ten — we picked it because we have ten fingers (the word digit means finger). Change the base, change the multiplier per position; the machinery is identical.

Computers use base twobinary — because 1.1 gave us switches with two reliable states. Two symbols, 0 and 1, so each position is worth twice the one to its right: powers of 2 instead of powers of 10. Read the binary number 1011_2:

1\times2^3 + 0\times2^2 + 1\times2^1 + 1\times2^0 = 8 + 0 + 2 + 1 = 11_{10}

(The little subscript names the base, so 1011_2 can't be mistaken for a thousand-and-eleven.) Each binary digit is a bitbinary digit, a word coined by statistician John Tukey and made famous by Shannon (1.8). A bit is the smallest possible unit of information: one yes/no, one switch.

To convert the other way — decimal to binary — repeatedly divide by 2 and read the remainders bottom-to-top. 11 \div 2 = 5 r 1, 5\div2=2 r 1, 2\div2=1 r 0, 1\div2=0 r 1 → read up → 1011_2. Do it a few times and it becomes reflex; the pattern of powers (1, 2, 4, 8, 16, 32, 64, 128, \dots) is worth memorizing, because you'll recognize these numbers everywhere in computing.

2. Hexadecimal — because binary is unreadable

Binary is correct but exhausting: an 8-bit value like 10110110 is a wall of digits your eye can't chunk. The fix is a base that packs neatly into binary. Since 16 = 2^4, one base-16 digit encodes exactly four bits — no arithmetic, just a lookup.

Hexadecimal (base 16) needs sixteen symbols, so after 0–9 it borrows letters: A=10, B=11, C=12, D=13, E=14, F=15. Split any binary number into 4-bit groups (nibbles — half a byte, and yes that's the real, official term) and translate each nibble to one hex digit:

\underbrace{1011}_{\text{B}}\ \underbrace{0110}_{\text{6}} \;=\; \text{B6}_{16} \;=\; 182_{10}

That's why hex is everywhere an engineer looks: memory addresses (0x7ffe), colors in CSS (#FF5C00 — two hex digits each for red, green, blue), byte dumps, MAC addresses, error codes. The 0x prefix is the universal "this is hex" flag. Learn to see a hex digit as its nibble — F is 1111, 8 is 1000, A is 1010 — and raw binary stops being scary. (Octal, base 8, packs 3 bits per digit and survives mostly in Unix file permissions like chmod 755; you'll meet it in Part 2.)

10110110high nibblelow nibbleB6= 0xB61 byte = 8 bits = 2 nibbles = 2 hex digits
Figure 1 — Why hex exists. A byte splits cleanly into two 4-bit nibbles, and each nibble is exactly one hex digit. Hexadecimal is just binary, chunked so humans can read it.

3. The byte, and the units that scale from it

A single bit is too small to be useful, so computers group bits. Eight bits make a byte — the standard unit of "one character, one small number, one addressable cell of memory." Eight is a historical settling point (enough to hold one text character in the early ASCII/EBCDIC era, and a clean power of two); it stuck so hard that "byte" now means eight bits everywhere. One byte holds 2^8 = 256 distinct values (0–255, or −128 to 127 signed).

From the byte, the familiar ladder of sizes — and here lives a genuine, interview-worthy confusion, so let's kill it cleanly. There are two systems:

Decimal (SI, powers of 1000)Binary (IEC, powers of 1024)
kilobyte KB = 10^3 Bkibibyte KiB = 2^{10} = 1024 B
megabyte MB = 10^6 Bmebibyte MiB = 2^{20} B
gigabyte GB = 10^9 Bgibibyte GiB = 2^{30} B
terabyte TB = 10^{12} Btebibyte TiB = 2^{40} B
petabyte PB = 10^{15} Bpebibyte PiB = 2^{50} B
exabyte EB = 10^{18} Bexbibyte EiB = 2^{60} B
zettabyte ZB = 10^{21} Bzebibyte ZiB = 2^{70} B

The powers of 2 don't land on the powers of 10 — 2^{10} = 1024, not 1000 — so the two systems drift apart, faster the higher you go (at the tera level the gap is already ~10%). This is the source of the eternal "why does my 1 TB drive show as 931 GB?" complaint: the drive maker advertises in decimal (10^{12} bytes = 1 TB), while Windows divides by 1024s and labels the result "GB" — so it shows 10^{12} / 2^{30} \approx 931, calling gibibytes "GB." Nobody's lying; they're using different powers of the same bytes. The strictly-correct term for the 1024-based unit is kibibyte / mebibyte / gibibyte (KiB/MiB/GiB), coined by the IEC in 1998 precisely to end the ambiguity. RAM is always sold in binary units (a "16 GB" stick is really 16 GiB, because memory is addressed in powers of 2); disks and network speeds are usually decimal. Knowing which system a number uses is the whole trick. CS basics: storage sizes and units from bytes up to TB/ZB — and the KB-vs-KiB truth. [EQ-34]

A useful anchor

Roughly: a plain-text page ≈ a few KB, a photo ≈ a few MB, a movie ≈ a few GB, a large hard drive ≈ a few TB. Humanity's total stored data crossed into the zettabyte range (billions of TB) in the 2010s — which is why you now see EB and ZB in the news at all.

4. Representing negatives — the two's complement magic trick

So far, only non-negative numbers. How do you store −7 in switches that only know 0 and 1? The naive idea — steal the leftmost bit as a "minus sign" (sign-magnitude) — seems obvious and turns out to be a trap: it gives you two zeros (+0 and −0), and worse, your beautiful adder from 1.2 no longer works — adding the bit patterns for +5 and −5 doesn't yield zero. You'd need separate, complicated subtraction hardware.

The elegant answer, used by virtually every computer built since the 1960s, is two's complement. The rule for an n-bit number: the top bit's place value is made negative. In 8 bits, the positions are worth -128, 64, 32, 16, 8, 4, 2, 1. So the leftmost bit still acts like a sign (1 there means negative), but it's not a flag pasted on — it's a genuine negative weight.

Read 11111001 in 8-bit two's complement:

-128 + 64 + 32 + 16 + 8 + 0 + 0 + 1 = -7

The quick way to negate a number by hand: flip every bit, then add 1. Take +7 = 00000111 → flip → 11111000 → add 1 → 11111001 = −7. ✓

Why this specific scheme conquered the world: the same adder subtracts. Because the encoding wraps around modulo 2^n, adding the two's-complement bit patterns of, say, 5 and −5 produces exactly zero (the carry falls off the top and is discarded). The CPU needs one adder for both + and — feed it the negated number and addition is subtraction. It also has just one zero (00000000), no wasteful ±0. This is a recurring flavour of engineering elegance: choose the representation so the hardware gets simpler, and Chapter 1.4 will show the same instinct applied to fractions.

−1286432168421negative weightplace values of an 8-bit two's-complement integer — only the top bit is negative
Figure 2 — Two's complement. The most-significant bit carries a negative place value. That single change makes one adder do both addition and subtraction, with a single representation of zero.

5. Overflow — when a number outgrows its box

A register has a fixed width. An 8-bit signed byte holds only -128 to +127. So what is 127 + 1? In bits: 01111111 + 1 = 10000000, which in two's complement is… −128. Add one to the largest positive number and you get the most negative one. The value wrapped around the edge of its range. That's integer overflow, and it is not a rare curiosity — it is one of the most consequential bug classes in software history.

Overflow is silent by default: the hardware just discards the carry that fell off the top and moves on, so the wrong answer flows downstream with no alarm. The famous casualties are real: the Ariane 5 rocket self-destructed 37 seconds after launch in 1996 when a 64-bit velocity was force-fit into a 16-bit integer and overflowed. The "Gangnam Style" counter on YouTube visibly broke when views blew past 2^{31} (about 2.1 billion), the limit of a signed 32-bit integer — prompting a switch to 64-bit counts. The Year 2038 problem looms for the same reason: many systems count seconds since 1970 in a signed 32-bit integer, which overflows on 19 January 2038.

The lessons are permanent and worth carrying: pick an integer type wide enough for the worst case (2^{63} is astronomically large, which is why 64-bit is the modern default); know that your language may wrap silently (C, Go), throw (Python promotes to arbitrary precision), or be checked only in debug builds (Rust); and treat any arithmetic on attacker-influenced sizes or counters as a security surface, not just a correctness one. Overflow is where the tidy math of "an n-bit number" meets the hard edge of finite hardware.

6. Endianness — which end comes first

One last wrinkle that trips up every engineer eventually. A number wider than a byte — say the 32-bit value 0x0A0B0C0D — occupies four memory bytes. In what order do they sit? Two conventions exist. Big-endian stores the most-significant byte first (0A 0B 0C 0D), the way we write numbers left-to-right. Little-endian stores the least-significant byte first (0D 0C 0B 0A), which feels backwards but simplifies some hardware. The name is a joke from Gulliver's Travels — warring factions who crack boiled eggs at the big or little end — Danny Cohen's 1980 way of saying the choice is arbitrary but you must agree. x86 and ARM run little-endian; most network protocols specify big-endian ("network byte order"). It matters the moment bytes cross a boundary — saved to a file, sent over a wire, shared between different machines — which is why serialization code (Part 5) must convert explicitly. Inside one machine you rarely notice; between machines, get it wrong and 0x0A0B0C0D silently becomes 0x0D0C0B0A.

7. The expert lens

Encodings are chosen to make hardware cheap. Two's complement, hex-as-nibbles, powers of two everywhere — none of these are arbitrary. Each was picked so the circuit underneath gets simpler or the human reading it gets faster. This is a design instinct, not a set of trivia: when you later choose a database ID scheme, a serialization format, or a bit-packed protocol, you're making the same kind of representation trade-off, consciously now.

Powers of two are the grain of the machine. 256, 1024, 65536, 2^{31}, 2^{32}, 2^{63} — these numbers will haunt every limit you meet: the 255-character field, the 65,535 max port number, the 2.1-billion signed-int ceiling, the 4 GB address space of a 32-bit process. When a system mysteriously caps or breaks at one of these, an experienced engineer's first thought is "a power of two overflowed," and they're usually right.

Next chapter: integers were the easy part. 1.4 tackles everything else — fractions (and why 0.1 + 0.2 ≠ 0.3), text from ASCII to Unicode and UTF-8, and how the same bits become an image, a sound, or a color.

Recall

  • Positional notation: each place is worth (base) times the one right of it. Binary is base 2 (powers of 2); a bit is one binary digit.
  • Hex (base 16) packs 4 bits per digit, so one hex digit = one nibble; it's how humans read addresses, colors, byte dumps. 0x marks hex.
  • 8 bits = 1 byte (256 values). Sizes scale KB→MB→GB→TB→PB→EB→ZB in two systems: decimal (÷1000, SI) vs binary (÷1024, the KiB/MiB/GiB "-bibyte" units) — the source of "1 TB shows as 931 GB."
  • Two's complement makes the top bit's weight negative, so one adder does both + and with a single zero; negate by "flip all bits, add 1."
  • Fixed width means overflow: exceed the range and the value wraps silently (127+1 = −128) — the cause of Ariane 5, Y2038, and a major security surface. Endianness is which byte comes first when a number crosses a boundary.

Self-test: Convert 0xB6 to binary and to decimal. Why does one adder suffice for subtraction? What is 127 + 1 in a signed byte, and why? Why might a 1 TB disk display as 931 GB?

Quiz Bank

FoundationalConvert 0xB6 to binary and decimal, showing the nibble trick.

Each hex digit is one nibble: B = 1011, 6 = 0110, so 0xB6 = 10110110. In decimal: 128+32+16+4+2 = 182. (Or hex directly: 11\times16 + 6 = 182.) The point: hex ↔ binary needs no arithmetic, just per-nibble lookup.

FoundationalWhat is a nibble, and why does it matter?

A nibble is 4 bits — half a byte. It matters because 16 = 2^4, so a nibble maps to exactly one hexadecimal digit. That's the entire reason hex is the readable shorthand for binary: byte = two nibbles = two hex digits.

AppliedWhy does a 1 TB hard drive show as about 931 GB in Windows?

The manufacturer advertises in decimal: 1 TB = 10^{12} bytes. Windows divides by 1024 three times (bytes→KB→MB→GB) but labels the result "GB" — so it reports 10^{12} / 2^{30} \approx 931, which is really 931 gibibytes (GiB). Both count the same bytes; they use different powers (1000 vs 1024). The unambiguous binary units are KiB/MiB/GiB (IEC, 1998).

AppliedExplain two's complement and why it beat sign-magnitude.

In two's complement, the most-significant bit carries a negative place value (e.g. −128 in a byte), so negative numbers fall out of ordinary place value. Negate via "flip all bits, add 1." It beat sign-magnitude because (1) it has a single zero (no ±0) and (2) the same adder performs subtraction — adding the bit patterns wraps mod 2^n and gives the correct signed result, so no separate subtract hardware is needed.

InterviewWhat is integer overflow? Give a real-world failure it caused.

Overflow happens when an arithmetic result exceeds the fixed width of its type and wraps around — e.g. in a signed byte, 127 + 1 = -128. It's usually silent (the carry is discarded), so wrong values propagate unflagged. Real failures: the 1996 Ariane 5 explosion (a 64-bit velocity forced into 16 bits), YouTube's "Gangnam Style" view counter breaking at 2^{31}, and the looming Year 2038 problem (signed 32-bit Unix time). Mitigations: use 64-bit types, know your language's overflow behavior (wrap vs throw vs checked), and treat arithmetic on untrusted sizes as a security concern.

InterviewWhat is endianness and when does it actually matter?

Endianness is the byte order of a multi-byte value in memory: big-endian stores the most-significant byte first; little-endian stores the least-significant first. It only matters when bytes cross a boundary — written to a file, sent over the network, or shared between machines of different endianness. x86/ARM are little-endian; "network byte order" is big-endian, so serialization code must convert explicitly or 0x0A0B0C0D can arrive as 0x0D0C0B0A.

StaffA counter in your service is a signed 32-bit integer and traffic is growing 3× a year, currently at 400 million/day cumulative. What do you do, and how do you frame the risk?

Frame it as an overflow deadline. Signed 32-bit caps at 2^{31}-1 \approx 2.147 billion. At ~400M/day cumulative growth (and accelerating 3×/yr) the ceiling is weeks-to-months away, not years — and overflow is silent, so the failure mode is a counter suddenly going negative, corrupting downstream logic (quotas, IDs, ordering). Fix: migrate to signed 64-bit (\sim9.2\times10^{18}, effectively unbounded for a counter) before the deadline, audit every read/write and serialized format for the width assumption, and add a monitor/alert at, say, 80% of range as a safety net. The staff-level move is treating a numeric type as a capacity decision with a date attached.

Flashcards

FlashValue of binary 1011

8+2+1 = 11.

FlashBits per hex digit / per nibble

4 bits = 1 nibble = 1 hex digit.

FlashBits in a byte; values it holds

8 bits; 2^8 = 256 values (0–255 unsigned, −128…127 signed).

FlashDecimal vs binary size units

Decimal (SI): KB/MB/GB = powers of 1000. Binary (IEC): KiB/MiB/GiB = powers of 1024. They drift apart as size grows.

FlashNegate a two's-complement number

Flip every bit, then add 1.

Flash127 + 1 in a signed byte

−128 — it overflows and wraps around.

FlashBig-endian vs little-endian

Big-endian: most-significant byte first. Little-endian: least-significant byte first (x86/ARM). Network byte order = big-endian.

Scenario Drill

DrillYou're debugging a service that parses a binary file written by a C program on an x86 machine, but the integers come out as garbage — 16777216 where you expected 1. What's your first hypothesis and how do you confirm it?

16{,}777{,}216 = 2^{24} = \text{0x01000000}, whereas 1 = 0x00000001 — the byte order is reversed. First hypothesis: an endianness mismatch. The C program wrote a 32-bit int in the x86 machine's little-endian order (01 00 00 00), and your parser is reading it as big-endian, so the bytes are interpreted end-for-end. Confirm by dumping the raw bytes (a hex viewer) and checking whether the expected value appears byte-reversed. Fix: have the parser read with the correct, explicitly-specified byte order (or convert from network/host order), never relying on the ambient platform default. This is exactly why serialization formats pin their endianness.