Chapter 2

Why Only 0 and 1

Why Only 0 and 1

Computers could, in principle, use three states instead of two. Engineers thought about this long ago, and the Soviet Union actually built a working ternary (base-3) computer called Setun in the late 1950s. So why did the entire world converge on binary — just 0s and 1s?

The answer hides in the physical nature of electrical noise.

Core Principles

Voltage and Noise: The Physics Behind Binary

Information inside a computer is represented by voltage levels. Classic TTL circuits defined anything above 2.4V as "1" and anything below 0.4V as "0." Modern chips use lower voltages, but the principle is identical.

Picture a highway with only two lanes: one on the far left and one on the far right, separated by a very wide median strip.

Voltage
 5V |████████████| ← "1" zone (safe region)
    |            |
    |  Noise     | ← Wide median (voltage can wobble here
    |  Margin    |   without being misread)
    |            |
 0V |████████████| ← "0" zone (safe region)

If you used three voltage levels (0, 1, 2), you'd be cramming three lanes into the same 5V range — the median strips become narrower, and a little electrical noise could cause "1" to be misread as "2." With binary, you only have two zones, so the margin between them can be made very wide.

Binary wasn't chosen because it's mathematically simple. It was chosen because it maximizes noise immunity. That's an engineering decision, not a mathematical one.

Number Bases: Different Languages for the Same Quantity

Binary, decimal, and hexadecimal are just different ways of writing the same numbers — like different languages that all describe the same objects.

Take the number 42 as an example:

Decimal:      42
              = 4×10¹ + 2×10⁰

Binary:       101010
              = 1×2⁵ + 0×2⁴ + 1×2³ + 0×2² + 1×2¹ + 0×2⁰
              = 32 + 0 + 8 + 0 + 2 + 0 = 42

Hexadecimal:  2A
              = 2×16¹ + 10×16⁰
              = 32 + 10 = 42

Hexadecimal is beloved by programmers because exactly 4 binary digits map to exactly 1 hex digit — making it a compact shorthand for binary:

Binary   0010 1010
           ↓    ↓
Hex        2    A    →  0x2A

This is why memory addresses and color codes like #FF5733 are written in hex — it's human-readable binary.

Negative Numbers: The Elegance of Two's Complement

You might guess that negative numbers are represented by flipping the top bit to signal "negative." That approach exists (it's called sign-magnitude) but it has a fatal flaw: zero has two representations (+0 and -0), and the hardware for addition needs special cases to handle the sign bit. Messy.

Modern computers use two's complement. The rule is simple:

Positive numbers: plain binary
Negative numbers: flip every bit of the positive version, then add 1

Example (8-bit):
+5  =  0000 0101
-5  =  1111 1010  (flip all bits)
    +          1
    =  1111 1011  ← this is -5 in two's complement

The payoff is remarkable: subtraction becomes addition.

5 - 5 = 5 + (-5)
      = 0000 0101
      + 1111 1011
      = 1 0000 0000  ← overflow bit is discarded → 0000 0000 = 0 ✓

The CPU needs only one addition circuit to handle both addition and subtraction. This design choice ripples through every ALU (Arithmetic Logic Unit) ever built.

Fractions: IEEE 754 Floating-Point

Integers are fine for counting, but the physical world has fractional quantities. Computers represent fractions using floating-point numbers, standardized by IEEE 754.

A 32-bit single-precision float splits its bits into three fields:

Bit  31    30─23      22─0
      ↓      ↓           ↓
   [Sign][Exponent(8)][Mantissa(23)]

Value = (-1)^sign × 1.mantissa × 2^(exponent − 127)

Example: representing −6.5

−6.5 = −1.101 × 2²   (binary scientific notation)

Sign     = 1 (negative)
Exponent = 2 + 127 = 129 = 1000 0001
Mantissa = 101 0000 0000 0000 0000 0000

Full 32 bits: 1 10000001 10100000000000000000000

This also explains the infamous 0.1 + 0.2 ≠ 0.3 bug: 0.1 is a repeating fraction in binary, just as 1/3 is a repeating fraction in decimal (0.333…). The stored value is slightly wrong from the start, and errors accumulate.

Hands-On Verification

# Base conversion
n = 42
print(f"Decimal:     {n}")
print(f"Binary:      {bin(n)}")       # 0b101010
print(f"Hexadecimal: {hex(n)}")       # 0x2a

# Two's complement: manually compute -5 in 8 bits
def twos_complement(n, bits=8):
    if n >= 0:
        return format(n, f'0{bits}b')
    positive = format(-n, f'0{bits}b')
    flipped = ''.join('1' if b == '0' else '0' for b in positive)
    result = int(flipped, 2) + 1
    return format(result, f'0{bits}b')

print(f"\nTwo's complement (8-bit):")
print(f"+5 = {twos_complement(5)}")
print(f"-5 = {twos_complement(-5)}")

# Verify addition works
pos5 = int(twos_complement(5), 2)
neg5 = int(twos_complement(-5), 2)
total = (pos5 + neg5) & 0xFF   # keep only 8 bits
print(f"5 + (-5) = {total} (should be 0)")

# Floating point surprises
print(f"\nFloating-point traps:")
print(f"0.1 + 0.2 = {0.1 + 0.2}")
print(f"0.1 + 0.2 == 0.3? {0.1 + 0.2 == 0.3}")

import math
print(f"math.isclose(0.1+0.2, 0.3) = {math.isclose(0.1 + 0.2, 0.3)}")

# See the actual bits of a float
import struct
bits = struct.pack('>f', -6.5)
print(f"\n-6.5 as 32-bit float: {int.from_bytes(bits, 'big'):032b}")

Output:

Decimal:     42
Binary:      0b101010
Hexadecimal: 0x2a

Two's complement (8-bit):
+5 = 00000101
-5 = 11111011
5 + (-5) = 0 (should be 0)

Floating-point traps:
0.1 + 0.2 = 0.30000000000000004
0.1 + 0.2 == 0.3? False
math.isclose(0.1+0.2, 0.3) = True

-6.5 as 32-bit float: 11000000110100000000000000000000

🔬 Going Deeper

Why Not Ternary?

Ternary computing isn't theoretically inferior. The Soviet Setun (1958) reportedly ran reliably for years and was argued to be more energy-efficient than binary machines of its era. Researchers today are experimenting with three-state carbon nanotube transistors. The real obstacle isn't physics — it's path dependency. Decades of operating systems, compilers, algorithm libraries, networking protocols, and hardware interfaces have all been built around binary. The switching cost is astronomical. Setun is a historical curiosity; binary is civilizational infrastructure.

IEEE 754's Hidden Corner Cases

IEEE 754 reserves certain bit patterns for special values worth knowing: +Inf (positive infinity), -Inf (negative infinity), and NaN (Not a Number). Dividing a non-zero number by zero doesn't crash — it quietly returns Inf. Dividing zero by zero returns NaN. The truly sneaky part: NaN != NaN evaluates to true, meaning NaN is the only value in programming that isn't equal to itself. This breaks many naive "equality check" patterns. The 2008 revision of IEEE 754 also introduced decimal floating-point formats specifically for financial software, where representing 0.1 exactly is non-negotiable.

Recommended Reading

Rate this chapter
4.6  / 5  (95 ratings)

💬 Comments