Floating Point: Why 0.1 + 0.2 Isn't 0.3
Every language prints 0.30000000000000004 and every newcomer files a bug report. It's not a bug — it's binary fractions doing exactly what the IEEE 754 standard says. Here's the why, and what to do about it.
Every language prints 0.30000000000000004 and every newcomer files a bug report. It's not a bug — it's binary fractions doing exactly what the IEEE 754 standard says. Here's the why, and what to do about it.
Text feels like a solved problem until an emoji breaks your database, a filename won't delete, or 'MÜLLER' and 'müller' won't compare equal. The layered history of character encoding explains every mojibake bug you'll ever hit.
Wall clocks on different machines disagree by milliseconds — eternity for a fast system. Lamport's insight: you don't need time, you need causality. Lamport clocks, vector clocks, and the hybrid compromise running inside your database.
The same pattern runs in microseconds in Go and hangs for years in JavaScript, on the same input. The reason is a fork in engine design made in the 1970s — automata vs backtracking — and it's why one regex took Cloudflare's edge offline.
Every deadlock requires four conditions to hold simultaneously, and every practical cure breaks exactly one of them. Why lock ordering is the fix that survives contact with real codebases, and how to find the cycle when it happens anyway.
Before a language model sees your text, a tokenizer chops it into subword pieces from a fixed vocabulary — and that boundary explains the model's weirdest failures: bad arithmetic, the strawberry problem, why code indentation matters, and why some languages cost 3x more.
Between 'sum the bytes' and SHA-256 lies the workhorse category: non-cryptographic hashes. What avalanche means, how hash quality turns into hash-table throughput, and why every language runtime switched to seeded hashing.
Half of all confusing C build errors — undefined reference, multiple definition, static initialization order — are linker concepts wearing compiler costumes. Symbols, relocations, and the archive rule that surprises everyone.
Binary search loses to linear scan on small arrays. Hash maps lose to sorted vectors. Big-O tells you how algorithms scale, not which one is faster — and on modern hardware the gap between those two questions is enormous.
Signed integers could have been encoded a dozen ways. Hardware settled on two's complement because it makes the adder ignorant of sign — one circuit for everything. The same choice explains INT_MIN, sign extension bugs, and why abs() can return a negative number.
Between pressing enter and your first line of code, the kernel parses an ELF file, maps segments, loads an interpreter you didn't know existed, and that interpreter relocates half your program. A tour of the machinery under ./a.out.
At reset, an x86 CPU wakes up pretending it's 1978 — 16-bit mode, 1 MB address space. Everything after that is bootstrapping: firmware, boot sector or EFI application, protected mode, and finally a kernel. I wrote a bootloader to learn the path; here's the map.
Every process thinks it has a private, contiguous address space starting at zero. None of them do. Virtual memory is the translation layer that makes the lie hold — and it's why fork, mmap, and swap can exist at all.
Nobody ships textbook quicksort. Python runs Timsort, C++ runs introsort, and both are layered hybrids built around one insight: real-world data isn't random, and worst cases must be impossible.
Everyone knows floating point is 'imprecise.' Fewer know that the same expression can give different results on different machines, that summation order changes your answer, and why associativity — the thing algebra promised you — is a lie in float.
UTF-8 was sketched on a placemat in 1992 and now carries ~98% of the web. It won because of five design properties — self-synchronization, ASCII compatibility, sort-order preservation — that no committee would have dared require.
C has no destructors, no defer, no exceptions — just early returns that leak and nested ifs that march off the screen. The kernel settled this decades ago: goto-based cleanup is the correct idiom, and it's more disciplined than its reputation.
Every recursive call is a frame pushed onto the call stack — return address, saved registers, locals. See the frame layout once and stack overflows, tail calls, and the recursion-to-iteration transformation all become mechanical.