Exponent bits buy range; mantissa bits buy precision. Modern ML keeps trading precision away for memory and speed.
Open a Python REPL and type 0.1 + 0.2. You’ll get 0.30000000000000004. This isn’t a bug, and it isn’t Python being weird — it’s a fundamental property of how computers represent real numbers, and every language does it. Understanding why is the difference between trusting your numbers blindly and knowing where they’ll bite you. It’s also, surprisingly, one of the most economically important ideas in modern AI — the entire FP32 → BF16 → FP8 progression that makes today’s models affordable is a story about the bits in the diagram above.
Why floats approximate
Computers store numbers in binary, and most decimal fractions have no exact binary representation — just as 1/3 has no exact decimal representation (0.333…). The value 0.1 in binary is a repeating fraction, so it gets rounded to fit the available bits. Do arithmetic on rounded values and the tiny errors compound. 0.1 + 0.2 lands a hair away from 0.3, and the extra digits are that rounding surfacing.
The key mental shift: a floating-point number is an approximation with a bounded error, not an exact value. Once you internalize that, the rules follow.
The anatomy: sign, exponent, mantissa
A float splits its bits into three parts (top of the diagram):
- Sign (1 bit) — positive or negative.
- Exponent — sets the range (how big or small the number can be). It’s the scale, like the exponent in scientific notation.
- Mantissa (a.k.a. significand) — sets the precision (how many significant digits you get).
This is the trade at the heart of every format. More exponent bits = wider range. More mantissa bits = finer precision. You have a fixed budget of bits and you split it between the two. FP32 (the classic “float”) spends 8 bits on exponent and 23 on mantissa — lots of both. Everything interesting in ML is about spending that budget differently.
The rules that fall out
Because floats approximate, a few hard rules apply, and violating them causes real bugs:
- Never test floats for exact equality.
if x == 0.3is a latent bug. Compare within a tolerance (abs(x - 0.3) < 1e-9). - Order of operations matters. Floating-point addition isn’t associative:
(a + b) + ccan differ froma + (b + c). Summing a large number and many tiny numbers can lose the tiny ones entirely (“swamping”). This is why parallel reductions can give slightly different results run-to-run — the summation order changed. - Money doesn’t belong in floats. Use integer cents or a decimal type. You do not want
0.1 + 0.2deciding a financial balance. - Catastrophic cancellation — subtracting two nearly-equal large numbers annihilates the significant digits and leaves mostly noise.
None of these are exotic. They’re the everyday failure modes of treating an approximation like an exact number.
The AI-era relevance: this is the whole precision story
Here’s where a “boring fundamentals” topic becomes frontier infrastructure. Neural networks are enormous piles of floating-point numbers, and the format you store them in directly sets your memory footprint, your bandwidth, and your speed. Recall from the memory hierarchy that ML is memory-bound — so using fewer bits per number is one of the biggest levers there is. Halve the bits and you halve the memory and roughly double the effective bandwidth. The diagram is a map of that trade:
- FP32 — the old training default. Precise, but memory-hungry.
- BF16 (bfloat16) — the modern training workhorse, and a genuinely clever idea. It keeps FP32’s 8 exponent bits (same range) but slashes the mantissa to 7 bits (less precision). Why? Because in training, range matters more than precision — you need to represent both huge and tiny gradients without overflowing, and you can tolerate coarse precision. BF16 gives you FP32’s dynamic range in half the memory. That’s why it took over.
- FP16 — also 16 bits, but split differently (5 exponent, 10 mantissa). More precision, less range — which is why FP16 training famously needs “loss scaling” to stop small gradients from underflowing to zero. The BF16-vs-FP16 difference is entirely a bits-budget decision, and it has real consequences.
- FP8 and below — the frontier. Training and especially inference increasingly use 8-bit floats, cutting memory 4× versus FP32. This is why the newest accelerators advertise FP8 throughput.
Related: quantization (running inference in INT8/INT4) is the same idea taken further — trading numerical precision for a smaller, faster model, accepting a small, measured accuracy hit. And mixed precision training deliberately uses low precision for the bulk of the math while keeping a high-precision master copy of the weights, precisely to manage the accumulation errors this post is about.
Every one of these techniques is an application of the sign/exponent/mantissa trade-off. When you understand that floats are approximations and that the bit budget splits between range and precision, the entire modern-ML precision landscape stops being a grab-bag of acronyms and becomes one coherent idea: spend the fewest bits you can get away with, in the split your workload needs.
The takeaway
0.1 + 0.2 ≠ 0.3 isn’t a curiosity — it’s the visible edge of a fundamental truth: computers approximate real numbers, and the approximation has structure. That structure gives you the everyday rules (no exact equality, watch your summation order, keep money in integers) and it gives you the entire logic of low-precision machine learning. The reason a trillion-parameter model can be trained and served at a cost anyone can afford is that engineers got very deliberate about the bits in the diagram. Fundamentals don’t get more relevant than that.