Skills101

Computer Organisation & Architecture (COA) · Lesson 2 of 8 · Updated ·

Data Representation — Binary, Hex, Two's Complement, Overflow and IEEE 754 Floating Point

Inside the computer everything is bits. How those bits are interpreted — unsigned, signed, floating-point, character — is decided by the representation. Exams love these conversions, and real bugs (integer overflow, 0.1 + 0.2 != 0.3) come straight from them.

1. Binary and hexadecimal

Each binary digit is a power of 2. 1011₂ = 8 + 0 + 2 + 1 = 11. Hexadecimal groups 4 bits into one digit (0–9, A–F), which is why memory addresses and colours are written in hex.

n = 202
print("binary:", bin(n), "| hex:", hex(n), "| octal:", oct(n))
print("1011 0110 as decimal:", int("10110110", 2))
print("0xCA as decimal:", int("CA", 16))
print("hex digit groups of 11001010:", "1100 ->", hex(0b1100), "| 1010 ->", hex(0b1010))
binary: 0b11001010 | hex: 0xca | octal: 0o312
1011 0110 as decimal: 182
0xCA as decimal: 202
hex digit groups of 11001010: 1100 -> 0xc | 1010 -> 0xa

2. Signed numbers: three ways

How do we store −5 in 8 bits?

Method+5−5ZerosRange (8 bits)
Sign-magnitude0000 01011000 0101two (+0, −0)−127 to +127
1's complement (flip all bits)0000 01011111 1010two−127 to +127
2's complement (flip + 1)0000 01011111 1011one−128 to +127

Every modern CPU uses 2's complement: one zero, and addition/subtraction use the same adder circuit for signed and unsigned numbers.

Two's complement of 5 in 8 bits: start with 00000101, invert every bit to get 11111010, add 1 to get 11111011, which represents minus 5; the leftmost bit has weight minus 128

A neat way to read a 2's complement number: the most significant bit has negative weight. 1111 1011 = −128 + 64 + 32 + 16 + 8 + 0 + 2 + 1 = −5.

def to_twos(value, bits=8):
    return format(value & (2 ** bits - 1), f"0{bits}b")

def from_twos(bitstr):
    v = int(bitstr, 2)
    return v - 2 ** len(bitstr) if bitstr[0] == "1" else v

for v in [5, -5, -1, 127, -128]:
    b = to_twos(v)
    print(f"{v:5} -> {b} -> back to {from_twos(b)}")
    5 -> 00000101 -> back to 5
   -5 -> 11111011 -> back to -5
   -1 -> 11111111 -> back to -1
  127 -> 01111111 -> back to 127
 -128 -> 10000000 -> back to -128

Range of n-bit 2's complement: −2ⁿ⁻¹ to 2ⁿ⁻¹ − 1. For 8 bits: −128 to 127; 16 bits: −32,768 to 32,767; 32 bits: about ±2.1 billion.

3. Overflow

If the true result doesn't fit in the range, the stored result wraps around — overflow. Rule for 2's complement addition: overflow happens only when two numbers of the same sign give a result of the opposite sign (equivalently, carry into the sign bit ≠ carry out of it).

def add8(a, b):
    raw = (a + b) & 0xFF
    result = raw - 256 if raw >= 128 else raw
    overflow = (a >= 0) == (b >= 0) and (result >= 0) != (a >= 0)
    return result, overflow

for a, b in [(100, 20), (100, 50), (-100, -50), (100, -50)]:
    r, ov = add8(a, b)
    print(f"{a:5} + {b:4} = {r:5}  overflow={ov}")
  100 +   20 =   120  overflow=False
  100 +   50 =  -106  overflow=True
 -100 +  -50 =   106  overflow=True
  100 +  -50 =    50  overflow=False

100 + 50 "became" −106 in 8 bits. Real bugs like this have crashed rockets and frozen games; languages like Java silently wrap int the same way at 32 bits.

4. IEEE 754 floating point

Real numbers use scientific notation in binary: (−1)^sign × 1.mantissa × 2^(exponent − bias).

Single precision (32 bits): 1 sign bit, 8 exponent bits (bias 127), 23 fraction (mantissa) bits. Double precision: 1 + 11 (bias 1023) + 52.

IEEE 754 single precision layout: 1 sign bit, 8 exponent bits stored with a bias of 127, and 23 fraction bits with an implied leading 1

Worked example: −6.75

  1. Binary: 6.75 = 110.11₂
  2. Normalise: 1.1011 × 2²
  3. Sign = 1 (negative)
  4. Exponent = 2 + 127 = 129 = 1000 0001
  5. Fraction = bits after the point: 1011 followed by zeros

Let Python confirm it bit by bit:

import struct

def ieee754(x):
    bits = format(struct.unpack(">I", struct.pack(">f", x))[0], "032b")
    sign, exp, frac = bits[0], bits[1:9], bits[9:]
    print(f"{x:>8}: {sign} {exp} {frac}  (hex {hex(int(bits, 2))})")
    e = int(exp, 2)
    value = (-1) ** int(sign) * (1 + int(frac, 2) / 2 ** 23) * 2 ** (e - 127)
    print(f"          exponent {e} - 127 = {e - 127}, decoded back = {value}")

ieee754(-6.75)
ieee754(0.1)
   -6.75: 1 10000001 10110000000000000000000  (hex 0xc0d80000)
          exponent 129 - 127 = 2, decoded back = -6.75
     0.1: 0 01111011 10011001100110011001101  (hex 0x3dcccccd)
          exponent 123 - 127 = -4, decoded back = 0.10000000149011612

0.1 has no exact binary representation (like 1/3 in decimal), so the stored value is very slightly off — the root of 0.1 + 0.2 != 0.3 in every language.

Special values:

Exponent bitsFractionMeaning
all 00±0
all 0≠ 0denormal (tiny numbers)
all 10±∞
all 1≠ 0NaN (not a number)

5. Characters

  • ASCII: 7 bits, 128 characters ('A' = 65).
  • Unicode / UTF-8: every character in every script; UTF-8 uses 1 byte for ASCII and 2–4 bytes for others — Hindi letters take 3 bytes each.
for ch in ["A", "₹", "क"]:
    print(ch, "code point", ord(ch), "UTF-8 bytes:", len(ch.encode("utf-8")))
A code point 65 UTF-8 bytes: 1
₹ code point 8377 UTF-8 bytes: 3
क code point 2325 UTF-8 bytes: 3

Common mistakes

  • Forgetting to add 1 when taking 2's complement.
  • Using range −127..127 for 8-bit 2's complement (it's −128..127).
  • Forgetting the bias (127) or the implied leading 1 in IEEE 754.
  • Declaring overflow when adding numbers of opposite signs (impossible in 2's complement).

Quick recap

  • Hex = groups of 4 bits.
  • 2's complement = invert + 1; MSB has weight −2ⁿ⁻¹; range −2ⁿ⁻¹ … 2ⁿ⁻¹−1; one zero.
  • Overflow: same-sign operands, opposite-sign result.
  • IEEE 754 single: 1 | 8 (bias 127) | 23, value = (−1)ˢ × 1.f × 2^(e−127).
  • 0.1 isn't exact in binary; UTF-8 uses 1–4 bytes per character.

Practice what you learned

📝 Quiz: Data Representation5 questions · instant answers & explanations · +10 XP · counts towards your free certificateTake the quiz →