The arithmetic underneath

The numbers below the smallest one

Below the smallest normal number the spacing stops halving and stays put, all the way to zero. That is what gradual underflow is, and the thing it buys is the sentence every algorithm assumes without being told — x minus y is zero only when x equals y.

Worth reading first: What a float can hold · A norm that overflows before it is a norm.

A norm that overflows before it is a norm is about the top of the range, where the failure is loud: the answer is an infinity, the infinity is absorbing, and everything downstream becomes a NaN. Nobody misses it.

The bottom is a different story with a different lesson, and the lesson is one that took the standards committee years of argument to settle. It is also the only part of IEEE 754 that hardware manufacturers still routinely turn off.

The spacing of the numbers below the smallest normal, at 11 significand bitsA staircase of spacing against magnitude on logarithmic axes. It descends in steps from the right and then flattens to a horizontal line at the left. A dashed line drops away instead.10⁻⁷10⁻⁵10⁻³10⁻¹⁰10⁻⁸10⁻⁶magnitudespacing to the next numberthe smallest normalgradualflush to zerosmallest normal6.1·10⁻⁵smallest subnormal6·10⁻⁸octaves of subnormals10pairs that lie under FTZ10the spacing stops halving and stays putwhich is what makes x − y = 0 mean x = y
Fig. 1 The spacing of the representable numbers, all the way down. The staircase descends in doublings and then flattens: below the smallest normal the gap stops halving and stays constant, which is what “gradual” means. The dashed line is the same format with flush-to-zero. Drag the significand width and watch the flat section change length.

What is down there

What a float can hold establishes that the representable numbers are spaced by a gap that halves at every power of two going down. At some point the exponent field runs out. For fp16 that is 2⁻¹⁴ = 6.1·10⁻⁵, which is the smallest normal — the smallest number with the standard’s implicit leading one.

A format could stop there. Everything below the smallest normal would round to zero, the exponent field would be fully used, and the representable set would be a tidy geometric arrangement with a sudden hole in the middle.

IEEE 754 does not stop there. It reserves the bottom exponent code for numbers with an implicit leading zero instead of a one, which gives the subnormals: multiples of 2⁻²⁴ running from 2⁻²⁴ up to the smallest normal. Those are p − 1 further octaves of numbers, at a spacing that no longer halves, with fewer and fewer significant bits as they get smaller until the last one has a single bit.

The band’s width is a fact about the significand rather than about the exponent, and it is worth stating because it is the opposite of what the name suggests. The subnormals run from 2^(emin−p+1) to 2^emin, which is p − 1 octaves whatever the exponent field does. So a format with more precision has more to lose at the bottom, and the site checks that identity against the measured span at every position of the slider.

The sentence it buys

The reason all this exists is one property, and it is a property nobody thinks about because it is always true where it has been tested.

x − y = 0 implies x = y.

Every algorithm that tests a difference against zero relies on it. Every convergence test of the form “stop when the iterate stops moving” relies on it. Every deduplication, every equality-by-subtraction, every if (a != b) guarding a division by their difference relies on it.

Without subnormals it is false, and it is false in a specific and easily constructed way. Take x equal to the smallest normal and y the next representable number above it. Their difference is one ulp at that magnitude, which is 2⁻²⁴ in fp16 — below the smallest normal, so with flush-to-zero it becomes zero, while x and y are demonstrably different numbers.

The site counts them rather than describing them. Over the ten distinct pairs immediately above the smallest normal in fp16:

pairs that subtract to zero
with subnormals 0 of 10
flush to zero 10 of 10

All of them. Not a rare event on adversarial data — every single pair in that band, on data constructed by adding one ulp.

Why anybody turns it off

The obvious question, given the table above, is why flush-to-zero exists at all. Two answers, and both are about hardware rather than about mathematics.

Subnormals are slow. They are the case the fast path does not handle: an arithmetic unit built around a normalised significand has to detect the subnormal case, shift, and often trap to microcode or to software. On many processors an operation producing a subnormal result costs tens or hundreds of times what a normal one costs. A loop that wanders into the subnormal band can slow down by two orders of magnitude with no other symptom, which is a genuinely alarming performance cliff and one that has caused real production incidents.

And the values are usually meaningless anyway. A quantity that has decayed to 10⁻³⁸ in a single-precision computation is, in most physical settings, zero — and the argument that its last three significant bits are worth preserving is hard to make.

So compilers offer a fast-math flag that flushes, GPUs flush by default in some modes, and a great deal of production code runs with the property above simply not holding.

The counter-argument is what makes it worth an essay rather than a footnote. The performance cliff is a reason to avoid producing subnormals, not a reason to redefine them as zero. The two responses are different: rescaling the computation so it does not enter the band keeps the identity and removes the slowdown, while flushing removes the slowdown and breaks the identity. The first requires knowing something about the data and the second does not, which is exactly why the second is the one that ships.

The spacing of the numbers below the smallest normal, at 5 significand bitsA staircase of spacing against magnitude on logarithmic axes. It descends in steps from the right and then flattens to a horizontal line at the left. A dashed line drops away instead.10⁻⁶10⁻⁴10⁻²10⁻⁸10⁻⁶10⁻⁴magnitudespacing to the next numberthe smallest normalgradualflush to zerosmallest normal6.1·10⁻⁵smallest subnormal3.8·10⁻⁶octaves of subnormals4pairs that lie under FTZ4the spacing stops halving and stays putwhich is what makes x − y = 0 mean x = y
Fig. 2 The same picture at five significand bits, where the subnormal band is four octaves instead of ten. The width of the flat section is the precision, not the exponent range — so a low-precision format has a narrower cushion at the bottom as well as fewer digits everywhere.

Gradual, and what it means quantitatively

The word in the standard is gradual underflow, and it is doing precise work.

Without subnormals, the smallest representable positive number is 6.1·10⁻⁵ in fp16, and the next thing below it is zero. A value at 3·10⁻⁵ has nowhere to go: it becomes zero, and its relative error is 1. The error jumps from one part in two thousand to one hundred per cent in a single step, which is the cliff.

With subnormals, a value at 3·10⁻⁵ rounds to a multiple of 2⁻²⁴ and keeps nine of its eleven bits. A value at 3·10⁻⁶ keeps six. A value at 10⁻⁷ keeps one. Precision degrades one bit at a time across the band, which is what “gradual” means, and the relative error rises smoothly from u to 1 across ten octaves instead of jumping at one.

The site checks the arithmetic of that band directly: the subnormals are evenly spaced, so a value at one and a half times the smallest one rounds to twice it, and below half of the smallest one there is nothing left. Both are assertions rather than descriptions, and the second is the boundary of the representable set — the point at which the whole apparatus does finally stop.

An honest accounting of what it buys

The strongest claims made for gradual underflow are stronger than the evidence, and it is worth separating them.

The identity above is real and it is checked. x − y = 0 if and only if x = y, ten pairs out of ten, in both directions.

The smoothness of the relative error is real and it is what the arithmetic of the band delivers.

The claim that it “saves” computations is mostly not. A computation whose intermediate values have decayed into the subnormal range is a computation in trouble for reasons that a few extra bits at the bottom will not repair. The band is ten octaves wide; the loss going into it is already catastrophic on any relative measure. Subnormals turn a discontinuity into a slope, and a slope down to zero is still a slope down to zero.

What they genuinely prevent is a spurious zero — a difference, a residual, a denominator that becomes exactly zero when it should be small, and then divides. That failure is not gradual and does not announce itself, which is precisely the pattern this site keeps finding: the dangerous failures are the ones that produce a well-formed answer of the wrong kind.

The spacing between consecutive numbers at 11-bit precisionA log–log staircase of the gap between neighbouring representable numbers against magnitude. The gap doubles at every power of two and reaches one whole unit partway along.110³10⁶10⁹10¹²10¹⁵10¹⁸10⁻¹⁸10⁻¹⁵10⁻¹²10⁻⁹10⁻⁶10⁻³110³magnitude of the numbergap to the next representable numberat 1: 9.8·10⁻⁴at a million: 512gap reaches 1: 111-bit significandthe gap follows the magnitude
Fig. 3 The staircase without the bottom, which is how this site drew it for two phases. Every step is a doubling and the sequence runs forever — a description that is exactly right in the middle of a format’s range and has no way to say where the range ends.

The argument that settled it

The subnormals were the most contested item in the standard, and the argument that carried them is worth knowing because it is not the one a reader would guess.

The case against was straightforward and was made by people who build arithmetic units: an extra case in the hardware, an extra branch on the critical path, and a benefit confined to values so small that nobody should be relying on them.

The case for was not “the numbers are useful”. It was that without them, a fundamental algebraic identity is false, and the identity is one that every programmer assumes without knowing they have assumed it. That is the x − y = 0 test above, and the argument’s force is that a violation of it does not look like a numerical problem. It looks like a logic error: a branch taken that should not have been, a division by a denominator that was tested and found nonzero and then computed as zero somewhere else, an iteration that reports convergence because two distinct iterates subtracted to nothing.

The generalisation is the useful part. The standard is full of choices that could have gone either way on accuracy grounds and went the way they did on predicate grounds — signed zeros so that 1/(+0) and 1/(−0) differ and branch cuts behave; the exact rounding of the arithmetic operations so that a comparison means what it says; the requirement that the operations be correctly rounded rather than merely close, so that two implementations agree bit for bit.

Every one of those is a choice to make the arithmetic predictable rather than to make it accurate, and they are two different objectives. This site spends most of its length on accuracy, so it is worth naming the other one once: a great deal of what makes floating point usable is not that the answers are close but that the same expression gives the same answer, and that the ordinary laws a program’s control flow depends on continue to hold. Subnormals are one of those laws, bought with an awkward case in the hardware.

Which also explains why the flag exists. Turning off gradual underflow costs almost nothing in accuracy — the values involved are tiny — and costs a predicate. A user optimising for a benchmark’s running time is not measuring predicates, and a user whose code silently takes a wrong branch six months later is not measuring anything.

Detecting it, which is harder than it should be

One practical note, since the failure is silent by construction.

There is no portable way to ask “did this computation produce a subnormal”. IEEE 754 defines an underflow exception flag, most hardware raises it, and most high-level languages give no access to it — JavaScript, which this site is written in, has none at all, which is why the measurements here are made by comparing two simulated contexts rather than by reading a flag.

What is available in practice is cruder and still useful. Compare two runs, one with the fast-math flag and one without, and look at whether the answers differ; a difference means the computation entered the band. Watch for the performance cliff, since a loop that suddenly runs a hundred times slower has almost certainly found the subnormals. And rescale on principle where the dynamic range of the data is known: a computation whose values are held near unity does not visit either end of the format, which removes this failure and the previous essay’s at the same time.

That last one is the recurring answer in this field and it is worth stating plainly, since two essays have now arrived at it. Most range failures are scaling failures. Overflow at the top and underflow at the bottom are the same phenomenon seen from opposite ends — data whose magnitudes are not where the format is — and the fix in both cases is to move the data rather than to widen the format. The formats have narrowed by a factor of three in significand bits over the last decade and by a factor of two in exponent bits, and the practice of scaling, which double precision made unnecessary for thirty years, is becoming load-bearing again.

What is asserted here

The subnormal band is p − 1 octaves, checked against the measured span between the smallest subnormal and the smallest normal at every significand width on the slider. Two routes: one counts octaves from the two constants, the other is the arithmetic identity.

With subnormals, no two distinct neighbours subtract to zero. Ten pairs, none of them.

And without them, every one of the ten does. Both directions, because the first alone would be consistent with a format in which the second was also true.

The subnormals are evenly spaced, checked by rounding one and a half of the smallest one to exactly two of them — which is the property that distinguishes the flat section of the staircase from the doubling section above it.

And below half the smallest subnormal there is nothing, which is the bottom of the representable set and the assertion that the band does end somewhere.

The refusals: that flush-to-zero leaves subtraction faithful, fed a pair one ulp apart at the smallest normal; that overflow is a rounding error near the answer; and that fp16 and tf32 are one format because their significands match. All three are the same shape of mistake — a property of the significand mistaken for a property of the format — and all three are refused.

The two ends, side by side

The phase’s arithmetic field ends with a symmetry that is not a symmetry, and it is worth setting out because the two failures get treated as one thing and are not.

At the top, the failure is loud, absorbing and unrecoverable within the format. An infinity propagates, a NaN propagates further, and no later step repairs either. What can be done about it is everything: rescale, and it does not happen. The scaled norm of the previous essay is three lines long and removes the failure entirely.

At the bottom, the failure is silent and partial. A value loses bits one at a time, and the worst outcome — a difference that should be small becoming exactly zero — is a well-formed number that no flag reports. What can be done about it is less: subnormals soften the landing and do not stop the descent, and rescaling helps only if the small values are small for a reason the scaling can express.

So the two ends of a format’s range are not mirror images. One is a cliff with a guard rail three lines long, and the other is a slope with a well-formed wrong answer at the bottom of it. The one that gets the attention is the first, and the one that has quietly been switched off across most of the hardware anybody runs is the second.

A 16-bit budget, split between range and precisionTwo curves against the width of the exponent field. One rises steeply and one falls in a straight line. Vertical lines mark the splits real hardware formats use.3456789101100.250.50.751bits in the exponent fieldeach curve as a fraction of its own maximumbfloat16fp16rangeprecisionto 617 decadesto 3.9 digitswhat the split buysbfloat16: largest number3.4·10³⁸fp16: largest number6.6·10⁴a bit of exponent doubles the rangea bit of significand adds a third of a digit
Fig. 4 And the budget it all comes from. The exponent field decides where the two ends are; the significand decides both how finely the numbers are spaced in between and — the part that is not obvious — how wide the cushion at the bottom is.
Where each norm works, for a vector of 16Four horizontal rows, one per format. Each carries a pale bar for the format's whole range, a bar above it for the scaled norm and a shorter bar below it for the naive one.-47-37-27-17-7313233301234log₁₀ of the vector's normfp1611 bitsbfloat168 bitstf3211 bitsbinary3224 bitspale: the format's range · blue: √(Σ(xᵢ/m)²)·m · red: √(Σxᵢ²)fp16 and tf32 have the same eleven significand bitsand their bars do not overlap
Fig. 5 The other end, measured. The pale bars include the subnormal region this essay is about, and the four exponents at the left where even the scaled norm loses accuracy are exactly the ones where the entries had already lost bits before any expression was applied.
The representable numbers with a 3-bit significandA number line from 0.5 to 4 with a tick at every representable value. The ticks are evenly spaced inside each power-of-two interval and twice as far apart in the next one up.[½, 1)[1, 2)[2, 4)0.5124gap 0.125gap 0.25 — twice as wide8 values per octavespacing doubles at each power of two
Fig. 6 Where the site started, at three significand bits. Eight numbers to an octave, the gap doubling at every power of two — and, in that drawing, no smallest number and no largest, because a significand alone does not have either.
Relative error of two algebraically identical expressions for (1 − cos x)/x², in 11-bitA log–log plot of relative error against x. The expression written as it reads loses accuracy as x falls and is eventually wrong in every digit; the rearranged form stays at rounding level.10⁻⁶10⁻⁴10⁻²110⁻⁵10⁻²10¹xrelative error of the computed value(1 − cos x)/x², as written2 sin²(x/2)/x²no digits left at all11-bit throughoutone function, two spellings · zero below 0.031
Fig. 7 The failure this one is most often confused with. Cancellation destroys the digits of a difference between two nearby numbers and returns a number with a large relative error; underflow returns a number that is exactly zero. The first is what the significand does and the second is what the exponent does, and until this phase the site could only draw one of them.

What links here

Computed from the collection, not written here: the essays that point at this one.

Shares its objects with

Essays that name at least two of the same things, and that neither author linked.

Named objects

A flat tag is an object no other essay names yet.

Flush-to-zeroGradual underflowHalf precisionIEEE 754OverflowRelative errorSignificandSubnormal numbers