Information Representation Explained

September 2, 2026 • 9 min read

Information Representation Explained
Table of Contents

Part of the series:Computers

At the lowest level, a computer knows only two things: on and off. Every photo, song, spreadsheet, and video call is, underneath it all, a long sequence of ones and zeros. But a pile of bits means nothing by itself. What gives bits meaning is representation: the agreed-upon conventions that map bit patterns to numbers, text, and richer data structures.

This post walks through those conventions from the ground up: binary and hexadecimal1, unsigned and signed integers, the elegant trick of two’s complement2, floating-point numbers3, characters and Unicode4, and finally how all of it composes into the data structures programs actually use.

Bits, binary, and hexadecimal

A bit is a single binary digit: 0 or 1. Eight bits form a byte, which is the smallest addressable unit of memory on most modern machines. With n bits you can represent 2^n distinct values: 1 bit gives 2 values, 8 bits give 256, 32 bits give about 4.3 billion.

Humans rarely read raw bits, so we group them. Binary1 is base 2, where each position is a power of two:

1 1 0 1 0 1 0 0
128 + 64 + 0 + 16 + 0 + 4 + 0 + 0 = 212

Hexadecimal5 (base 16) exists for one practical reason: a single hex digit maps to exactly four bits (a nibble), so it compresses binary without the mental gymnastics of decimal conversion:

binary:  1101 0100
hex:     D    4        -> 0xD4 (212 in decimal)

That is why you see hex everywhere in computing: memory addresses (0x7ffee4a3b8c0), color codes (#FF5733), and byte dumps. The 0x prefix (and 0b for binary, as in 0b11010100) is just a notational convention borrowed from C. Whenever you see hex, read it as shorthand for bits, not as a different kind of number.

Unsigned integers: counting from zero

The simplest interpretation of a bit pattern is the unsigned integer: plain positional binary, ranging from 0 to 2^n - 1. An 8-bit unsigned byte (uint8) holds 0 to 255; a 32-bit unsigned integer (uint32) holds 0 to 4,294,967,295.

Unsigned arithmetic is modular: it wraps around. Add 1 to a uint8 holding 255 and you get 0, because the 9th bit has nowhere to go (the carry falls off the end). This wraparound is not a bug in the hardware. Hardware performs this arithmetic modulo 2^n; programming languages differ in how their integer types expose or handle overflow.

Signed integers: the problem with the minus sign

Representing negative numbers requires a convention, and history tried several. In sign-magnitude, the top bit is the sign and the rest is the value: 0000 0101 is +5, 1000 0101 is -5. In ones’ complement6, negatives are the bitwise inverse: 1111 1010 is -5. Both suffer from the same disease: two zeros (+0 and -0), which complicates every comparison, plus addition needs special-case logic around the sign bit.

The representation that won — used by virtually every modern CPU — is two’s complement2.

Two’s complement, in depth

Rule: to negate a number, invert every bit and add one.

+5  = 0000 0101
flip: 1111 1010
+ 1 : 1111 1011 = -5

Check it by adding them: 0000 0101 + 1111 1011 = 1 0000 0000. The 9th bit overflows the 8-bit register and vanishes, leaving 0000 0000. That is the whole magic: signed addition needs no special hardware. The same adder circuit works for signed and unsigned alike, because two’s complement is just arithmetic modulo 2^n with the upper half of the range reinterpreted as negative.

Consequences worth knowing:

  • Range is asymmetric. An 8-bit signed integer spans -128 to +127, not -127 to +127. The pattern 1000 0000 is -128, which has no positive counterpart (negating it overflows back to itself).
  • There is only one zero. 0000 0000 negated is 1111 1111 + 1, which wraps to 0000 0000. The negative-zero disease is cured.
  • The top bit still reads as a sign bit: 1 means negative, 0 means non-negative. That falls out of the math rather than being bolted on.
  • Sign extension is free. Widening an 8-bit -5 (1111 1011) to 16 bits just repeats the sign bit: 1111 1111 1111 1011. Same value, wider container.

When you see -5 in a debugger displayed as ...11111011, you are looking at two’s complement. This is the representation used by modern mainstream signed integer types.

Floating point: IEEE 754

Integers cannot express 3.14159 or the mass of an electron. For real numbers, the standard is IEEE 7543 floating point: scientific notation in binary.

A 32-bit float splits into three fields:

sign (1 bit) | exponent (8 bits) | fraction/mantissa (23 bits)

The value is (-1)^sign × 1.fraction × 2^(exponent - 127), where 127 is the bias that lets the exponent field stay unsigned while representing negative exponents. The leading 1. is implicit (not stored), which buys one extra bit of precision — except for denormalized numbers near zero, where the implicit bit becomes 0. to allow gradual underflow.

Worked example, -5.75 in single precision:

5.75 in binary = 101.11 = 1.0111 × 2^2
sign     = 1 (negative)
exponent = 2 + 127 = 129 = 10000001
fraction = 0111 followed by 19 zeros

bits: 1 10000001 01110000000000000000000  = 0xC0B80000

The standard also reserves special patterns: exponent all zeros means zero (signed: +0.0 and -0.0) and denormals; exponent all ones means infinity (fraction zero) or NaN — not a number (fraction nonzero), the result of 0.0/0.0 or sqrt(-1). NaN has the infamous property that it compares unequal to everything, including itself.

Two practical lessons from floating-point arithmetic7:

  1. Most decimal fractions are repeating decimals in binary. 0.1 cannot be stored exactly, which is why 0.1 + 0.2 evaluates to 0.30000000000000004 in doubles. Be careful when comparing the results of floating-point calculations with ==; rounding can make mathematically equal values differ slightly.
  2. Floats are for measurement, not money. Rounding error accumulates, so financial code uses integers of cents or decimal types. A 64-bit double (1 sign + 11 exponent + 52 fraction bits) provides substantially more precision and range, but does not eliminate rounding error.

Characters and Unicode

Numbers were the easy part. Text required the industry to agree on which number means which letter.

ASCII8 (1963) assigned the English alphabet, digits, and control codes to 7-bit numbers 0–127: A is 65, a is 97. One byte per character, beautifully simple — and useless for the rest of the world’s writing systems. The following decades produced a chaos of incompatible 8-bit extensions (“code pages”) where the same byte meant é in one country and Ω in another.

Unicode4 fixed this by separating two concerns: a universal catalog where every character gets a permanent number (a code point, written U+00E9 for é), and encodings that map code points to bytes. The dominant encoding is UTF-89, and its design is a small masterpiece:

U+0000–U+007F:  0xxxxxxx                              (ASCII, 1 byte)
U+0080–U+07FF:  110xxxxx 10xxxxxx                     (2 bytes)
U+0800–U+FFFF:  1110xxxx 10xxxxxx 10xxxxxx            (3 bytes)
U+10000–U+10FFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx (4 bytes)

The x bits carry the code point; the leading bits announce the length. Example: é is U+00E9 = 1110 1001 in binary, which fits the 2-byte template as 110_00011 10_101001 = bytes 0xC3 0xA9. The euro sign (U+20AC) needs three bytes: 0xE2 0x82 0xAC.

Three properties made UTF-8 win: ASCII text is valid UTF-8 unchanged (backward compatible), no ASCII byte can appear as part of a multi-byte sequence, making ASCII delimiters and control bytes easy to recognize), and you can resynchronize mid-stream after corruption. (UTF-1610 code units are used by Java and JavaScript, and Windows APIs use UTF-16 extensively; it encodes text in 2-byte units with surrogate pairs for characters beyond U+FFFF — which is why "😀".length is 2 in JavaScript.)

Classic text bugs all trace back to this layer: mojibake (decoding bytes with the wrong encoding), confusing character count with byte count, and assuming one visible glyph equals one code point (accents and emoji can combine several).

Representing data: from bytes to meaning

Once you have numbers and text, everything else is composition — plus one crucial idea: endianness11. A multi-byte value like 0x12345678 can be stored most-significant-byte-first (big-endian: 12 34 56 78) or least-first (little-endian: 78 56 34 12, as on x86). File formats and network protocols must pick one; getting it wrong silently garbles every number. This is why standards like PNG and TCP/IP specify byte order explicitly.

Common building blocks:

  • Booleans and enums: one byte (or one bit in a bitfield) holding 0/1; enums map names to small integers.
  • Arrays: elements laid out back-to-back; indexing is just base_address + index × element_size.
  • Structs/records: fields concatenated, often with padding bytes inserted so each field lands on an aligned address the CPU can read efficiently. A struct’s size is not always the sum of its fields.
  • Strings: either length-prefixed (Pascal style: length first, then bytes) or null-terminated (C style: bytes ending in 0x00) — the latter being a classic source of buffer overflows.
  • Media: a pixel is three bytes (red, green, blue); audio is thousands of amplitude samples per second; video is images plus math that exploits what eyes won’t notice. JPEGs and MP3s are just very clever agreements about which details to throw away.

And the deepest lesson of all: a type is an interpretation, not a property of the bits. The 32 bits 0x40490FDB are the float 3.1415927 — and simultaneously the unsigned integer 1078530011. The bits never changed; only the lens did. Every cast, every reinterpret, every debugger “view as” command is a reminder that meaning lives in the convention, agreed upon by whoever wrote the bytes and whoever reads them.

Footnotes

  1. Binary number — Wikipedia 2

  2. Two’s complement — Wikipedia 2

  3. IEEE 754 — Wikipedia 2

  4. Unicode — Wikipedia 2

  5. Hexadecimal — Wikipedia

  6. Ones’ complement — Wikipedia

  7. Floating-point arithmetic — Wikipedia

  8. ASCII — Wikipedia

  9. UTF-8 — Wikipedia

  10. UTF-16 — Wikipedia

  11. Endianness — Wikipedia

Tags: