anthropocene
Computational Physics / Numerical Reality
◆ foundation18 min

The numbers you do not have

Your machine cannot represent 0.1, cannot represent most of the reals, and its arithmetic is not associative. Everything else in this path is built on top of that.

Start with the thing everyone has seen and nobody is told the reason for.

0.1 + 0.2 === 0.3   // false
0.1 + 0.2           // 0.30000000000000004

The usual explanation — "floating point is inexact" — is true and useless. It suggests a small random fuzz sprinkled over otherwise-real numbers. What is actually happening is sharper than that, and once you see it, the rest of numerical computing stops being a list of gotchas and becomes a single consistent story.

A double is 64 bits: one sign bit, eleven exponent bits, and fifty-two mantissa bits, encoding

That is a finite set. There are at most doubles, and the real line is not finite, so almost every real number you can name is simply not in the set. is not in the set — in binary it is the repeating fraction , and it gets rounded to the nearest available neighbour before your program does anything at all.

what a float64 is
0011111111110000000000000000000000000000000000000000000000000000
sexponent (11)mantissa (52)
value1.0000e+0
gap to next (1 ulp)2.220e-16
relative gap2.22e-16
consecutive integers?intact

The relative gap stays near machine epsilon everywhere; it is the absolute gap that grows with magnitude. That is why error is naturally measured relatively.

Drag that up past and watch the last readout. The gap between neighbouring doubles becomes larger than 1, which means consecutive integers stop existing: x + 1 === x evaluates to true, and no error is raised. The number simply has nowhere to go.

predictcommit first

You add 1.0 to a double, one billion times in a loop, starting from 2^53. What do you end up with?

The one operation that actually hurts

Multiplication and division are well behaved: relative errors add, slowly. Addition is fine too. Subtracting two nearly equal numbers is the operation that destroys you.

If and agree to twelve digits, then has only four meaningful digits left — and the arithmetic will hand you a result with sixteen digits printed, of which twelve are noise promoted to the front. Nothing warns you. The result looks fine.

catastrophic cancellation
10⁰10²10⁴10⁶10⁸10¹⁰10¹²10¹⁴10¹⁵10⁻¹⁶10⁻¹³10⁻¹⁰10⁻⁷10⁻⁴10⁻¹XRELATIVE ERROR OF NAIVE FORM
naive formmachine epsilon
naive
Math.sqrt(x + 1) - Math.sqrt(x)
rearranged
1 / (Math.sqrt(x + 1) + Math.sqrt(x))
worst relative error0.060
machine epsilon2.22e-16
digits lost≈ 14

Multiply by the conjugate: the subtraction disappears entirely. Both expressions are the same function on paper. They are not the same algorithm.

Both expressions in that panel are the same function. Not approximately the same — identically the same, provably, by algebra. Only one of them survives contact with a machine, because only one of them avoids forming a difference of near-equal quantities.

This is the distinction the whole field rests on:

  • Conditioning is a property of the problem. Some questions genuinely amplify input error, and no algorithm can rescue them.
  • Stability is a property of the algorithm. Two algorithms for the same well-conditioned problem can differ by ten orders of magnitude, as you just saw.
recallscheduled for review
Why does √(x+1) − √x lose accuracy for large x, and what is the fix?

What to carry forward

Every method in this path is going to make errors. The useful question is never "is there error" — there always is — but which of the two kinds is dominant, and what happens to it next:

  • Truncation error, from replacing a limit with a finite step. Shrinks as you refine.
  • Roundoff error, from finite precision. Grows as you refine, because refining means more operations and smaller differences.

Those two pull in opposite directions. The next lesson is where they collide head-on, and where the collision produces the single most important picture in the subject.