Bits, numbers and text
Every mysterious bug involving 0.1 + 0.2, mojibake or overflow is this stage, unlearned.
Three bugs follow every engineer around for a whole career: money that is off by a cent, text that renders as é garbage, and a number that silently became negative. All three are one chapter of knowledge, learned once. This is that chapter.
Integers — exact, until the edge
An n-bit integer holds exactly 2ⁿ values, and the machine represents negatives in two’s complement — a scheme chosen so addition needs no special cases. Two consequences matter. Overflow: one past the maximum wraps to the minimum — a 32-bit counter crosses 2,147,483,647 and becomes profoundly negative (ask anyone who stored view counts in an int32 in 2014). And signed/unsigned confusion: the same bits read as 4,294,967,295 or as −1 depending on the label. Rule of thumb that ages well: use 64-bit integers for anything that counts the real world, and treat every cast as a place a bug can stand.
Floats — approximate, by design
A 64-bit float is scientific notation in binary: a sign, 52 bits of digits, an exponent. It can hold astronomically large and small values because it gives up on holding most values exactly — 0.1 has no finite binary representation, so 0.1 + 0.2 === 0.30000000000000004, in every language, forever. That is not a bug; it is the contract. What follows from the contract:
- Money is never a float. Store integer minor units (cents, paise) or a decimal type. An accounting system that drifts by rounding is a system that lies.
- Never compare floats with
==; compare within a tolerance. - Adding a tiny number to a huge one may change nothing (the digits do not reach) — summing a large list in naive order loses precision.
- Integers up to 2⁵³ are exact inside a double — which is precisely why JavaScript, whose only classic number is a double, is safe with ordinary IDs and unsafe past
Number.MAX_SAFE_INTEGER. Snowflake-style 64-bit IDs (a later System Design chapter) must travel as strings in JSON for exactly this reason.
Text — numbers wearing costumes
Unicode assigns every character a number — a code point. An encoding then decides which bytes carry those numbers, and UTF-8 won that contest with a beautiful design: ASCII is unchanged at one byte, everything else uses two to four, and no byte of a multi-byte character looks like ASCII. Mojibake — é for é — is always the same crime: bytes written in one encoding, read in another. The professional posture: UTF-8 everywhere, declared explicitly at every boundary (database, HTTP header, file), and never inferred.
One more layer deserves respect: “one character” is a human idea, not a machine one. An emoji with a skin tone is several code points joined; é can be one code point or an e plus a combining accent. So length may count bytes, code points or neither, slicing can cut a character in half, and two visually identical strings can compare unequal until normalised. When text weirdness strikes, ask which of the three layers — bytes, code points, graphemes — each side of the bug is talking about.
Bytes on the wire
Multi-byte numbers have an order question — most significant byte first (big-endian, the network convention) or last (little-endian, most CPUs). Inside one machine you never notice; the moment bytes cross machines, a serialisation format is what saves you, by pinning the order, the width and the encoding in writing. That is half of what Protobuf and friends are for, and the Distributed Systems pathway picks the thread up properly.
The 2023 refresh of the classic. Code points, UTF-8, graphemes — the whole text story in one sitting.
The canonical reference. Skim the first half; keep it bookmarked for the day a float bug finds you.