The arithmetic underneath

A norm that overflows before it is a norm

The vector of sixteen thousands has a Euclidean norm of 4,000, which fp16 represents exactly. Written as the square root of the sum of squares it returns infinity, because squaring doubles the exponent — and the expression costs half the format's range on the one computation every iterative method performs at every step.

Worth reading first: What a float can hold · Cancellation takes the answer, not a digit.

This site has a thread for pairs of expressions that a textbook derivation cannot tell apart and a computer can. Classical against modified Gram–Schmidt. The normal equations against QR. Elimination with and without a row swap. In every case the algebra is identical, the arithmetic is not, and the difference is a loss of digits — a number that comes back with four correct figures instead of fifteen.

This is the same thread with a different failure at the end of it. The two expressions here differ in whether they return a number at all, and they differ over half the format’s range.

Where each norm works, for a vector of 8Four 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. 1 Where each expression works. Each format gets a pale bar for its whole range, a bar for the scaled norm and a shorter one for the naive expression. Drag the vector length and watch the red bars shrink from the right while the blue ones do not move.

The two expressions

The Euclidean norm of a vector is √(Σxᵢ²), and written that way it is one line:

s = 0
for each x:  s = s + x*x
return sqrt(s)

The alternative divides by the largest entry first:

m = max |x|;  if m = 0 return 0
s = 0
for each x:  t = x/m;  s = s + t*t
return m * sqrt(s)

They compute the same thing. Both are backward stable, both make the same number of rounding errors to within a small constant, and the second costs one extra pass over the data and one division per entry.

The second is what hypot does, what every BLAS nrm2 does, and what almost nobody writes.

Why squaring is expensive in a way that has nothing to do with cost

Multiplication adds exponents. So xᵢ² has twice the exponent of xᵢ, and a value comfortably inside the top of the range has a square that is not.

fp16’s largest finite number is 65,504, so anything above 256 has a square that overflows. The Euclidean norm of a vector whose entries are all 1,000 is 4,000 — representable, with three significant figures to spare — and the sum of squares is 1.6·10⁷, which is not a number fp16 has.

Half the range is gone, in the logarithm, and it is gone from the top. The measurement says how much:

format normal exponents naive works over scaled works over
fp16 30 18 (60%) 30 (100%)
bfloat16 254 131 (52%) 254 (100%)
binary32 254 138 (54%) 254 (100%)

Every one of the format’s normal exponents is tried: the same unit vector is rescaled by every power of two the format holds, both expressions are evaluated, and the answer is compared against the true norm. The scaled expression is right at every normal exponent in all three formats. The naive one is right over between 52% and 60% of them.

The 60% for fp16 rather than 50% is the vector length doing its work at the small end: with n = 8 the sum of squares of tiny entries is eight times a single square, which lifts the bottom of the naive window slightly above where the squares alone would put it.

The pairing the site could not draw before

The comparison this whole extension exists for takes one line and it is a vector of sixteen entries, each 1,000.

fp16: the naive expression returns infinity. tf32: the naive expression returns 4,000, which is right. bfloat16: the naive expression returns 3,968, which is right to within its precision.

fp16 and tf32 have the same eleven significand bits. Every measurement this site made before the maturity phase gave them identical answers, because the simulation had no exponent. Here one of them returns the answer and the other returns an infinity, on identical data at identical precision.

And the third line is the one worth carrying away. bfloat16 is the least precise of the three and the one that succeeds where the most precise of them fails. It returns 3,968 rather than 4,000 — an error of 0.8%, which is exactly what eight significand bits are worth — while fp16 returns nothing usable at all. That is the entire argument for bfloat16 in three numbers, and it cannot be made on an axis that measures only precision.

The site asserts all three: infinite in fp16, finite and correct in tf32, and finite in bfloat16 with a larger error than tf32’s — the last one in that direction, because “the less precise format did better” is a sentence that would be false if read without the qualifier.

Where each norm works, for a vector of 64Four 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. 2 The same measurement at a vector length of sixty-four. The red bars have retreated from the right, because the sum of n squares is n times the largest one and every doubling of n costs another factor of two at the top. The blue bars have not moved, because every ratio the scaled expression forms is at most one whatever n is.

Where this bites

The Euclidean norm is not an unusual computation. It is what every iterative method computes at every step, to decide whether to stop.

Conjugate gradients compute ‖r‖ each iteration. GMRES computes it as part of the Arnoldi orthogonalisation. Every stopping test in the rate the condition number predicts and the spectrum that predicts nothing is a norm ratio. Householder QR computes a norm to build every reflector — which is a reflection cannot stop being one’s subject, and where a spurious infinity would produce a reflector that is not a reflector.

At double precision none of this matters and that is why it is invisible. binary64’s range is 616 decades and half of it is 308, which no ordinary computation approaches. The problem appears exactly when the format narrows, which is exactly what the hardware has spent the last decade doing.

It is also why the standard library functions exist. hypot(x, y) is in every language’s maths library and it is there for one reason: √(x² + y²) written directly overflows when the answer does not. The scaled norm is that function generalised to n entries, and the reason nobody writes it by hand is that at double precision the naive version works and the habit never forms.

What the scaled version costs, and where it is not free

The extra pass over the data is not nothing on a large vector, and the division per entry is worse than the multiply it accompanies. On a memory-bound computation the second pass roughly doubles the cost of the norm.

Which is why real implementations do not do exactly what the code above does. The standard approach is a single pass with a running scale: carry both a scale factor and a running sum, and rescale only when an entry arrives that is larger than the current scale. On ordinary data the rescale happens once or twice and the cost is close to the naive loop’s; on adversarial data it happens more often and the routine is still correct. That is a genuinely better algorithm than either of the two above and it is not drawn here, because the argument this essay is making is about the two-expression comparison and a third expression that is better than both would obscure it.

There is a second approach worth naming because it is what the fast paths actually do. Compute the naive sum, and check the exception flag afterwards: if nothing overflowed, the answer is right and was obtained at full speed; if something did, fall back to the scaled routine. IEEE 754’s sticky flags exist for exactly this pattern. It costs one branch per call rather than one branch per entry, which is the difference between a technique and a tax.

Neither of those is available in this site’s simulation, since a JavaScript number has no flags to check. What is available is the measurement of which expression works where, and that is the claim the figures make.

Relative error of two algebraically identical expressions for (1 − cos x)/x², in binary32A 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⁻⁴10⁻²110⁻⁹10⁻⁶10⁻³1xrelative error of the computed value(1 − cos x)/x², as written2 sin²(x/2)/x²no digits left at allbinary32 throughoutone function, two spellings · zero below 3.5·10⁻⁴
Fig. 3 The other end of the same subject, and the reason this site can tell the two apart. Cancellation loses digits — the answer comes back with four correct figures instead of fifteen — and the loss is relative, bounded and analysable. Overflow loses the number.

Two failures that are not the same failure

It is worth being precise about why overflow gets its own essay when cancellation takes the answer already covers “an expression that is bad in a way the algebra hides”.

Cancellation is a relative error and it is bounded. Subtracting two nearly equal numbers returns a result whose relative error is large and whose absolute error is not: the answer is still within an ulp of the difference of the stored values, and the trouble is that those stored values were already rounded. Every backward error analysis in this site’s error field rests on that boundedness.

Overflow is not an error of any size. The result is an infinity, so the relative error is infinite, and infinity is absorbing: a subsequent subtraction gives a NaN and the NaN propagates through everything downstream. A single overflow does not degrade the answer, it removes it.

The practical consequence is the one worth having. There is no way to compute more accurately than the format permits — the response to cancellation is a better-conditioned expression or a longer significand. But overflow can always be engineered around, by scaling, at a cost of one extra pass or one exception check. A failure that has a fix and a failure that does not are different kinds, and the fix is three lines.

The scaling that is not a scaling

There is a variant of this problem which the two expressions above do not cover and which is worth naming, because it is where the fix stops being three lines.

The scaled norm works because every entry can be divided by the largest one without loss: the ratios are at most one, so nothing overflows, and they are at least the smallest ratio in the data, so nothing underflows unless the data already spans the whole range. That argument needs the entries to be comparable. If a vector holds one entry at 10³⁵ and one at 10⁻³⁵, dividing by the first sends the second to 10⁻⁷⁰, which is not a binary32 number, and the scaled expression returns the larger entry alone.

That is the right answer to fifteen digits — a Euclidean norm is dominated by its largest entry when the spread is that wide — so nothing is lost here. It stops being the right answer in the computations where the small entries are the point, which is most of what this site is about: the residual of a converged iteration, the trailing singular values, the entries below a rank cutoff.

The general fix is to carry more than one accumulator — one for the entries near the top of the range, one for the middle, one for the bottom — and combine them at the end. That is what the carefully written library routines do, and it is why a well-implemented nrm2 is a hundred lines rather than five. The measurement here does not reach it, because a unit vector rescaled uniformly has no spread at all, and building a test with the spread would be building a test of a routine this site does not implement.

What the site does record is where its own scaled expression stops being right: four exponents at the bottom of each format, all of them subnormal, where the entries have already lost significand bits before any expression is applied. Reporting those rather than restricting the measurement to the range where the claim holds is the difference between a check and an illustration.

What is asserted here

The scaled expression is right at every normal exponent of fp16, bfloat16 and binary32 — 30, 254 and 254 of them, with no exceptions.

The naive one covers between 45% and 62% of them, asserted in both directions. The upper bound is the claim; the lower one exists because an expression that covered a quarter of the range would be a different and much worse expression, and asserting only the upper bound would not notice.

And the scaled expression loses accuracy only among the subnormals, at most four exponents in each format, where the entries themselves have already lost significand bits before any norm is taken. That is reported rather than excluded — a routine described as covering “the whole range” and measured only over the normal part is a claim measured where it holds.

fp16 returns infinity where tf32 returns 4,000, at the same precision, and bfloat16 returns a finite answer less accurate than tf32’s.

The refusals: that fp16 and tf32 compute the same thing, which is the claim this site was making by omission for two phases; and that overflow is a rounding error near the answer, fed 300² in fp16.

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 The budget the whole thing follows from. fp16 and bfloat16 sit three exponent bits apart, which is twenty-four doublings of range for three significand bits — and it is the reason one of them survives the computation above and the other does not.
Relative error of three summation algorithms in binary32A log–log plot of relative error against the number of terms for naive, pairwise and compensated summation, each measured against the exactly rounded sum.10¹10²10³10⁴10⁵10⁶10⁻⁹10⁻⁷10⁻⁵10⁻³10⁻¹number of terms addedrelative error against the exact sumin orderin a treecompensatedbinary32 · terms are 1/icompensated: 3·10⁻⁸
Fig. 5 The neighbouring problem in the same loop. Adding n numbers accumulates error with n, which is the significand’s story about a sum; the exponent’s story about the same sum is whether it arrives at all.
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. 6 And the bottom end, where the scaled expression’s own four bad exponents live. Down there the entries have already lost precision before any expression is applied, which is the next essay’s subject.

The habit this suggests, and the one it does not

The advice that follows is narrower than “always use the scaled version”, and worth stating carefully because the broad version is wrong.

At double precision, write whichever is clearer. Six hundred decades of range is more than any physical quantity spans, the naive expression covers three hundred of them, and a computation that leaves that window has a scaling problem the norm is not going to fix. Rewriting every norm in a double-precision code for this reason would be work spent on a failure that is not going to happen.

At sixteen bits, do not write either — call the library. Twelve decades of range with six usable by the naive expression is a window narrow enough that ordinary data leaves it, and the correct routine is more subtle than the scaled loop above. This is the same conclusion the road that squares the problem reaches about the normal equations, and for the same reason: an expression that is fine in one regime and disastrous in another is exactly the kind of thing a library exists to get right once.

And in between, know which regime the data is in. That is not an evasion. The question “what is the largest magnitude this computation will produce” has an answer for most real codes, it is usually knowable in advance, and knowing it settles the question completely.

What the essay does not support is the stronger claim that the naive expression is a mistake. It is the right expression at double precision, it is faster, and it is clearer. What makes it a mistake is writing it into a routine that will later be compiled at a narrower precision by somebody who was not thinking about its range — which is a description of most of what has happened to numerical software in the last five years, and the reason this essay is in the maturity phase rather than the foundation.

Iterative refinement from a 11-bit factorisation, κ = 10⁴A semi-logarithmic plot of forward error against refinement step. One curve falls steeply to the level of a double-precision solve; the other is nearly flat.012345610⁻¹⁶10⁻¹³10⁻¹⁰10⁻⁷10⁻⁴10⁻¹refinement step‖x − x*‖ / ‖x*‖a full double-precision solveresidual in11-bitresidual indoubleone argument apartκ·u of the factorisation4.9double residual, final0.0049same-precision, final1.430×30, κ = 10⁴, same factors in both runsidentical cost
Fig. 7 Where narrow formats are actually being used, and why any of this is current. Iterative refinement from an eleven-bit factorisation recovers double-precision accuracy when κ·u permits — on data that has to stay inside the format’s range for the factorisation to exist at all.

What links here

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

Reads more easily once this is understood

Essays that name this one as worth reading first.

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.

bfloat16Catastrophic cancellationDynamic rangeHalf precisionhypotOverflowScaled normSignificand