Binary, Hexadecimal & Number Representation

Boolean Algebra & Digital Logic

Chapter 9 · Binary, Hexadecimal & Number Representation

Every chapter so far has quietly assumed numbers arrive as bits. This chapter asks the question directly — and answers a mystery Chapter 2 left open along the way: why ~True really does equal -2, not by accident, but by design.

Binary and Hexadecimal: The Same Number, Two Notations

Binary is positional notation base 2. Hexadecimal (base 16) exists because it aligns perfectly with binary in a way decimal never can: each hex digit represents exactly 4 bits, with no remainder.

Verified directly
173 in binary: 10101101. Split into 4-bit groups: 1010 and 1101 — decimal 10 and 13, i.e. hex A and D. Result: 0xAD, confirmed to equal 173 exactly.

Two's Complement: How Negative Numbers Actually Work

A naive "sign bit" scheme (flip the top bit to mean negative) has two real problems: it creates two representations of zero (+0 and -0), and ordinary addition breaks unless the hardware special-cases signs. Two's complement avoids both: to negate a number, invert every bit, then add 1.

Verified directly — 4-bit two's complement encodings
+3 = 0011. Inverting gives 1100; adding 1 gives 1101 — the encoding of -3. +5 → -5 = 1011. There's exactly one encoding of zero (0000), and the 4-bit range is -8 to 7 — deliberately asymmetric, one more negative value than positive, since 0 itself uses up one of the 16 available patterns on the positive side.

The Real Payoff: Chapter 7's Own Adder Needs No Changes at All

This is the entire reason two's complement is universal: a ripple-carry adder built with zero awareness of signs correctly computes negative results, purely because of how the bit patterns are chosen.

Verified directly, using Chapter 7's own unmodified 4-bit adder
Adding 0011 (+3) and 1101 (-3) through the exact same full-adder chain from Chapter 7: result bits 0000, with a discarded final carry-out of 1zero, exactly correct. Adding 0101 (+5) and 1011 (-5): same result, 0000. No special subtraction circuit, no sign-checking logic — the identical hardware from Chapter 7 handles both positive and negative numbers correctly, simply because the encoding was chosen to make that true.

Closing the Loop: Why ~True Really Is -2

Chapter 2 flagged ~True == -2 as a real, verified gotcha without explaining why. In two's complement, bitwise complement and negation are related by a clean identity:

Verified directly — the identity, and Chapter 2's own mystery resolved
~x = -x - 1, for every value checked: ~5=-6 (-5-1=-6), ~10=-11, ~100=-101, all matching exactly. Applied to Chapter 2's own case: True acts as 1, so ~True = -1-1 = -2 — not a quirk of Python's own boolean handling, but two's complement working exactly as designed, on a value that happened to be a boolean.

Arithmetic vs. Logical Right Shift

Right-shifting a negative number needs to decide what to fill the vacated high bits with. Python's >> is an arithmetic shift — it fills with the sign bit, preserving negativity and behaving like floor division by 2.

Verified directly
-8 >> 1 = -4. -7 >> 1 = -4 (floor of -3.5). -1 >> 1 = -1 — stays negative forever under repeated arithmetic shifting, never reaching 0 the way a logical (zero-filling) shift would. Some languages (Java's >>>, for instance) offer a separate logical shift operator specifically because arithmetic and logical shifts genuinely disagree on negative inputs.

Real Relevance

Bitwise operators (&, |, ^, ~, <<, >>) operate directly on these representations — Chapter 2's own flags/bitmasks are binary representation in action, and every value stored anywhere in a running program is, physically, one of the encodings this chapter just formalized.

Number Representation in Code

def to_twos_complement(x, bits): if x < 0: x = (1 << bits) + x return format(x, f"0{bits}b") print(to_twos_complement(-3, 4)) # '1101' # the ~x = -x-1 identity, verified generally for x in [5, 10, 100]: assert ~x == -x - 1 print("~x == -x-1 confirmed")

Hands-On Exercises

Exercise 1

Convert 202 to binary, then group it into 4-bit nibbles and convert each nibble to hexadecimal, confirming your hex result equals 202 when checked directly.

📄 View solution
Exercise 2

Encode +6 and -6 in 4-bit two's complement (showing the invert-then-add-1 process for the negative value), then add the two 4-bit patterns using Chapter 7's own full-adder chain, showing each stage's SUM and COUT, and confirm the result is 0000 with the carry-out discarded.

📄 View solution
Exercise 3

Using this chapter's own ~x = -x-1 identity, predict the value of ~(-5) without computing it directly first, then verify your prediction. Explain in one sentence why applying ~ twice in a row (~~x) always returns the original value x.

📄 View solution

Chapter 9 Quick Reference

  • Hex aligns exactly with binary — each hex digit is 4 bits, no remainder
  • Two's complement: invert all bits, add 1 — fixes sign-magnitude's double-zero and broken-addition problems
  • 4-bit range is -8 to 7 — deliberately asymmetric, exactly one representation of zero
  • Verified directly: Chapter 7's own unmodified adder correctly computes +x + (-x) = 0 — no special subtraction circuitry needed
  • Closed the loop on Chapter 2: ~x = -x-1~True=-2 was two's complement working exactly as designed
  • Arithmetic right shift sign-extends negative numbers; logical shift zero-fills — genuinely different results, real cross-language relevance
  • Next chapter: Capstone — designing a small digital circuit