// HACKER NEWS — CYBERSECURITY
Double-double: 31 digits of precision without leaving the FPU
If you ever need more precision than what 15 decimal digits of the double format can offer, there is a neat trick: glue two doubles together and treat them as one number. This gives you ~31 decimal digits for roughly 9x the cost of a plain double in a real kernel (4-12x per isolated operation). With no heap allocation and no dependencies, this puts it almost exactly halfway between a double and an arbitrary-precision library perf-wise. This post explains the error-free transformations that make it work, measures it against MPFR, and shows where the trick runs out of steam.
Floating-point types come in many "flavors".
For example, a float has ~7 decimal digits, a double ~15, both basically for free.
An arbitrary-precision library gives you as many digits as you want, at a painful per-operation cost.
Between "not quite enough" and "orders of magnitude slower" there is a gap and I fell into it while zooming deep into the Mandelbrot set.
The image at the top of this page shows this gap: the same view rendered twice, blocky on the left where a double has run out of precision, sharp on the right rendered with double-double.
The Mandelbrot set is a famous fractal, full of endlessly repeating spirals and mini copies of itself, that I have explored before.
A deep zoom literally runs out of precision.
Eventually, two neighboring pixel positions are "rounded" to the same double,
and the image stops being a picture of the fractal and starts being a picture of the number format,
as you can see in Figure 1.
The canonical answer to "I need more precision than double" is to use a library for arbitrary-precision math, typically GMP or MPFR.
That is the right answer when the precision you need is open-ended.
But it is not a small leap to take, and you pay for it on every single operation:
And then there is the license: GMP and MPFR are LGPL, which static linking, closed platforms, or company policy can turn into a hard no, regardless of benchmark results.
I benchmarked all of that, and the gap turned out to be surprisingly well structured.
In a real kernel at matched precision, a double-double costs roughly 9x a plain double, and MPFR carrying those same 31 digits costs roughly 9x a double-double again, which is about 81x the double it started from.
Let MPFR allocate a result per operation and the double-double-to-MPFR gap is closer to 40x instead of 9x.
About 31 decimal digits, no heap, no dependencies, and no native binary to ship.
The complete type and the benchmarked kernels are in the two appendices.
A floating-point type holds the same number of significant digits no matter how large or small the number is.
The exponent only determines where the decimal point will be.
Take these two numbers:
Each of them has 12 significant digits, so each fits in a double.
Their exact sum is 111222333444.555666777888 and has 24 digits, but a double can only hold about 15.
Evaluate A + B and the digits that do not fit are rounded away, leaving 111222333444.55566.