Foundationsbinaryhexadecimalbitsrepresentationmasks

Binary, and Why Everything Is Eventually Bits

Binary is not a numbering curiosity you convert for exam questions. It is the substrate: instructions, addresses, permission flags, protocol headers and floating-point values are all bit patterns, and hexadecimal exists because humans cannot read them otherwise.

▶ Run the labFollow the mechanism

Software view, hardware view

The gap between what you wrote and what the machine does is where this whole domain lives.

The question
Why does everything in computing eventually come down to binary, and what does that actually change about how I read a value?
What you wrote
Numbers are numbers. `42` is forty-two, and the fact that a computer stores it in base two is a historical implementation detail that leaks only into interview questions.
What the hardware does
A wire is either near supply voltage or near ground, and everything else is built on that one distinction. A value is a pattern of bits whose *meaning* comes entirely from how it is interpreted: the same 32 bits can be an integer, a float, four characters, an instruction, or an address, and the hardware does not record which.
Once you see the bit pattern as the real object and the type as an interpretation, a whole class of behaviour stops being mysterious: why masks work, why casting can be free or catastrophic, why hex appears in every memory dump, and why a protocol specification talks about bit offsets rather than numbers.
SourceCompilerInstructionsFront EndExecutionRegistersCachesMemoryI/OBehavior

The same bits, several different meanings

The reason binary matters is not conversion arithmetic. It is that hardware stores patterns and attaches no type to them. A 32-bit word holding 0x41424344 is simultaneously the integer 1094861636, the four ASCII characters ABCD, a plausible memory address, and — on some encodings — a valid machine instruction. Which one it *is* depends entirely on what reads it.

This is why casting between a float and an integer of the same width can either preserve the value or reinterpret the pattern, and why those are two completely different operations that look similar in source. It is why a buffer overflow can turn data into an instruction pointer. And it is why every protocol specification defines fields by bit offset rather than by number: the number is downstream of the layout.

The practical habit this should build is asking, when something surprising happens to a value, *what pattern is actually stored* — not what the variable is declared as. A debugger showing hex is showing you the ground truth; the decimal rendering is an interpretation the tool chose for you.

One 32-bit pattern, read five ways
InterpretationValueWhere you would meet it
Unsigned integer1094861636A counter, a length, an index
Signed integer (two's complement)1094861636Positive here; the top bit is clear — see Two's Complement: One Circuit for Addition and Subtraction
Four ASCII bytesABCDA file magic number, a protocol tag
IEEE-754 single-precision floatapproximately 12.14Reinterpreted, not converted — see Floating Point: Trading Precision for Range
Memory address0x41424344A pointer; famously what you see after corrupting one with text

Hexadecimal, and why it is everywhere

Hex is not a third number system anybody thinks in. It is a compression of binary for human eyes: exactly four bits per hex digit, so the mapping is positional and lossless, and you can read a byte as two digits without doing arithmetic. Decimal has no such alignment — 200 tells you nothing about which bits are set, whereas 0xC8 tells you immediately that it is 1100 1000.

That alignment is the entire reason tooling speaks hex. Memory dumps, addresses, colour values, permission bits, hash digests and instruction encodings are all displayed in hex because the display preserves the bit structure. Once you can convert a hex digit to four bits by reflex, a memory dump stops being noise.

The dump below is the sort of thing a debugger shows. Note that the address column, the byte column and the interpretation column are three views of the same storage — and that the ASCII column is only meaningful because someone decided those bytes were text.

A memory dump: address, raw bytes in hex, and one possible interpretation
Address      Bytes (hex)                                       ASCII
0x7ffd4a20   48 65 6c 6c 6f 20 77 6f 72 6c 64 00 00 00 00 00   Hello world.....
0x7ffd4a30   2a 00 00 00 ff ff ff ff 00 00 80 3f 00 00 00 00   *..........?....
             ^^ 42          ^^ -1 as int32  ^^ 1.0f as float

Each hex digit is exactly four bits:
  0x2a  =  0010 1010  =  42
  0xff  =  1111 1111  =  255 unsigned, -1 as signed 8-bit
  0x3f800000            =  1.0 interpreted as IEEE-754 single precision

Bit operations are how hardware asks questions

Because values are patterns, the cheapest possible operations are the ones that work on patterns directly: AND, OR, XOR, NOT and shifts. Each is a single gate per bit and completes in one cycle on essentially every CPU, which makes them the fastest arithmetic available.

The idiom that follows is the mask: build a pattern with ones where you care, AND it against the value, and you have isolated a field. Every permission system, flag register, protocol header parser and packed struct uses this, because packing several small values into one word and extracting them with masks costs almost nothing.

What makes masks worth understanding rather than copying is that they explain why certain "clever" optimisations are not clever at all. x & 7 is genuinely equivalent to x % 8 for unsigned x because 8 is a power of two and the low three bits *are* the remainder — but the compiler already knows that, and writing it by hand buys nothing while costing readability. The value is in reading such code, not producing it.

Masking: packing four small fields into one 32-bit word and reading them back
1// A packed RGBA colour: four 8-bit fields in one 32-bit word.
2const r = 0x12, g = 0x34, b = 0x56, a = 0x78
3
4const packed = (r << 24) | (g << 16) | (b << 8) | a // 0x12345678
5
6// Extract by shifting the field down, then masking off everything above it.
7const red = (packed >>> 24) & 0xff // 0x12
8const green = (packed >>> 16) & 0xff // 0x34
9const blue = (packed >>> 8) & 0xff // 0x56
10const alpha = packed & 0xff // 0x78
11
12// Testing a flag is an AND; setting is an OR; toggling is an XOR.
13const READ = 0b100, WRITE = 0b010, EXEC = 0b001
14let perms = READ | WRITE // 0b110
15const canWrite = (perms & WRITE) !== 0 // true
16perms = perms & ~WRITE // clear it: 0b100
17perms = perms ^ EXEC // toggle it: 0b101

Key points

  • Hardware stores patterns and attaches no type; the type is an interpretation applied by whatever reads the bits.
  • The same 32 bits can be an integer, a float, four characters, an address or an instruction.
  • Hex is used everywhere because four bits map to one digit exactly, preserving the bit structure that decimal destroys.
  • Bitwise operations are one gate per bit and among the cheapest instructions available.
  • Masks let several small fields share one word and be extracted almost for free — the basis of flags, permissions and packed formats.

Binary Explorer

Change an input and watch which number moves — and which one refuses to.

Click any bit
decimal
42
hex
0x2A
binary
00101010

Each bit is worth twice the one to its right. Nothing about the pattern says whether it is a number, a character, part of an instruction or an address — that is decided entirely by what reads it.

Follow the mechanism

The path through the machine, hop by hop — and the conclusions it invites that are wrong.

  1. 1
    Value → storage: a value is written as a pattern of bits into a register or a memory location, with no record of its type.
  2. 2
    Instruction → interpretation: the instruction that reads it decides what the pattern means — an integer add and a float add read the same bits differently.
  3. 3
    Mask → AND gate array: a bitwise AND drives one gate per bit position in parallel, completing in a single cycle.
  4. 4
    Shift → barrel shifter: a shift moves every bit by a fixed distance in dedicated hardware rather than by repeated addition.
  5. 5
    Result → register: the output is another pattern, again with no type attached, ready for the next interpretation.
What people conclude from this — wrongly
  • Believing a bit trick is faster than the equivalent arithmetic because it looks lower-level — the compiler emits the same instructions for both.
  • Confusing a value conversion with a reinterpretation, which produces wildly wrong numbers rather than a compile error in languages that allow it.
  • Treating hex as a different kind of number rather than as a rendering of the same bits.
  • Assuming a bit pattern that renders as sensible text actually is text; it is a coincidence of encoding, and a common source of false confidence when debugging.

Consequences, controls and cost

What it causes
  • • Reinterpreting a pattern (a "bit cast") and converting a value are different operations that can look nearly identical in source.
  • • Packed representations save memory and bandwidth at essentially no computational cost, which is why protocols and file formats use them heavily.
  • • A corrupted value often displays as recognisable text or a nonsensical pointer, which is a useful diagnostic signal rather than noise.
  • • Reading hex fluently turns memory dumps, disassembly and protocol traces from opaque output into readable evidence.
What you can do
  • • Read hex directly rather than converting to decimal; four bits per digit makes the bit pattern visible at a glance.
  • • Use masks and shifts to pack related small fields when memory or bandwidth matters — the extraction cost is negligible.
  • • Prefer named constants over literal masks; the operation is cheap but an undocumented magic number is not.
  • • Do not hand-optimise arithmetic into bit tricks for speed; compilers already do this, and the readability cost is real.
How to see it
  • • Print values in hex when debugging anything representational — an unexpected `0xffffffff` or `0x41414141` identifies the bug immediately.
  • • Inspect the compiler output to confirm that arithmetic you expected to become a shift actually did, rather than assuming it.
  • • Use a debugger's memory view to see the stored pattern rather than the language's rendering of it.
What it costs
  • • Packed fields save space but make values harder to read, harder to change later, and easier to get wrong when the layout evolves.
  • • Bit manipulation is dense; code that is clever with masks is disproportionately hard to review and to modify safely.
  • • Fixed-width packing bakes in limits — a field that was generously sized eventually is not.

Scope

§224 — what these claims are specific to.

What these claims are specific to
  • GENERALBinary storage and bitwise operations are universal across essentially all contemporary hardware. Shift semantics for negative or oversized shift counts are language-defined and differ — in C++ some are undefined behaviour, in JavaScript the count is masked to five bits.
  • ABI-SPECIFICHow multi-byte values are laid out in memory is a platform decision, not a property of binary — see Endianness: Which Byte Comes First.

Misconceptions

Claim
“Binary is just how computers store decimal numbers.”
Reality
Binary is how they store everything — instructions, addresses, text and floats included. The number interpretation is one of many, and the hardware does not know which one applies.
Claim
“Bit tricks make code faster.”
Reality
Optimising compilers already convert division by a power of two into a shift, modulo into a mask, and much more besides. Hand-written bit tricks usually produce identical machine code and worse readability.
Claim
“Hex is used to save space in output.”
Reality
It is used because four bits map to exactly one hex digit, so the bit structure survives the rendering. Decimal destroys that alignment, which is why no memory dump uses it.