What quad precision actually means (and how we made float128 real)
Doubling the bits in a float doesn't double the digits — it roughly doubles them. Here is what binary128 buys you, why we chose the representation we did, and how we kept every digit alive from source literal to storage.
Most of the time, a 64-bit double is enough, and most of the time nobody thinks about it. Then you subtract two nearly-equal numbers, or accumulate a sum a few million terms long, or iterate a map that amplifies error, and the last few digits of your answer turn to noise. Doubling to quad precision is the sledgehammer for that problem — and building it correctly is more subtle than "use twice as many bits," because the hardest part isn't the arithmetic, it's making sure the precision you asked for actually survives the trip from the number you typed to the bytes in memory.
Equana has a real Float128 type. This post is about what quad precision buys you, the representation choice behind it, and the specific engineering that keeps every digit alive from a source literal through to storage — because if a quad value silently rounds through a 64-bit JavaScript number at construction, you have a type that lies about its own precision.
IEEE 754, in two formats
Floating-point numbers are defined by IEEE 754, and the format everyone uses is binary64 — "double precision." It spends its 64 bits as 1 sign bit, 11 exponent bits, and 52 stored fraction bits (53 with the implicit leading 1). Those 53 bits of significand are the whole story on precision: they give you a hair under 16 decimal significant digits, and a machine epsilon — the gap between 1.0 and the next representable number — of about 2⁻⁵² ≈ 2.2 × 10⁻¹⁶.
Binary128 — "quadruple precision" — is the same idea with a much wider significand: 1 sign bit, 15 exponent bits, and 112 stored fraction bits (113 with the implicit one). The quad format gives roughly 34 significant decimal digits and a machine epsilon around 2⁻¹¹² ≈ 1.9 × 10⁻³⁴.
The number to internalize is that one: 34 digits, not 32. Doubling the storage does not double the digits, because precision scales with the significand bits, and binary128 has 113 of them against binary64's 53 — a bit more than double. The wider exponent also pushes the overflow ceiling and underflow floor far out, but for numerical work the significand is what you're buying. Those extra ~18 digits are guard digits: room for cancellation, accumulation, and conditioning to eat away at without touching the answer you actually care about.
What the extra digits are for
Quad precision is not about printing more decimals for show. It is about the digits you lose along the way to a result that only needs a handful.
Subtract 1.0000000000000001 from 1.0000000000000002 in double and the leading sixteen digits cancel, leaving you a result computed from the one or two noisy bits at the bottom — catastrophic cancellation. Sum ten million terms and the rounding error of each addition accumulates until the tail of your total is meaningless. Iterate a chaotic or stiff system and tiny perturbations get amplified exponentially. In every case the fix is the same: carry more precision through the computation than the final answer needs, so the error has somewhere to hide that isn't your significant digits. Quad's 34 digits give a 64-bit problem 18 digits of headroom — usually the difference between "converged" and "diverged into rounding noise."
In Equana Float128 is a first-class dtype, not a formatting flag. It sits in the type lattice exactly where you'd expect — Float128 <: AbstractFloat <: Real <: Number — so it participates in multiple dispatch like any other numeric type, and there is a matching complex type built from two quad components. You get a quad value either by conversion, float128(x), or by writing a literal with the f128 suffix.
Double-double vs true binary128
There are two ways to implement quad precision, and the choice is a real engineering fork.
True binary128 is a single 113-bit significand implemented in software: you lay out the 128 bits yourself and write the add, multiply, and divide routines that manipulate that layout directly. It is the "correct" IEEE format to the last bit, and it is slow, because no mainstream CPU has hardware for it — every operation is a software routine over a custom bit layout.
Double-double represents a quad value as an unevaluated sum of two doubles — a high word and a low word, where the low word holds the part that didn't fit in the high word. Its arithmetic is built from clever "error-free transformation" sequences (the two-sum and two-product tricks) that capture the rounding error of each hardware double operation and carry it in the low word. Double-double gives you about 106 bits of significand — a touch less than binary128's 113 — but every operation is a short sequence of hardware double instructions, so it runs an order of magnitude faster than fully-software binary128, and it maps cleanly onto a format made of two IEEE doubles.
Equana's Float128 uses the double-double approach, and the storage layout shows it directly: the dtype is 16 bytes — two 64-bit words, a high/low pair, and the complex quad type is simply two of those. That is the pragmatic choice for a browser-and-native numerics library: it reuses the machine's fast double hardware, it stores and moves as two plain Float64 words through the same WebAssembly kernels and shared memory as everything else, and it delivers the ~34 digits that make quad worth having. The 113th bit of pedantic binary128 is not worth an order of magnitude of speed for the problems people actually reach for quad to solve.
Keeping the digits alive from literal to storage
Here is the part that makes it real rather than nominal. A quad type is only as good as its weakest link, and the easiest place to lose precision is the very first step: constructing the value.
The trap is subtle. If a literal like 3.14159265358979323846f128 is parsed into a 64-bit host number and then widened to Float128, the extra digits are already gone — they were rounded off the moment they touched a double, and no amount of quad arithmetic afterward can bring them back. You'd have a 16-byte value carrying a 53-bit answer: a type that reports quad precision while holding double precision. That is the failure mode to design against.
The fix is to never let a quad literal pass through a host double on its way in. Equana's lexer recognizes the f128 suffix while it is still scanning characters and carries the number forward as its full decimal text — the complete digit string, suffix attached — rather than eagerly converting it to a host number. The digits survive as text until the quad-aware backend parses them straight into the double-double high/low representation, so the significand is filled from all the digits you wrote, not from a pre-rounded approximation. Precision enters the system at full width and stays there.
The complement to building it right is proving it right. Because the low word is exactly the digits a bare double would have thrown away, you can see the difference in a verification cell: construct the same constant as Float64 and as Float128, and the quad value carries correct digits precisely where the double has already gone to zero. Those checks are what turn "we have a Float128 dtype" into "we have quad precision you can trust" — the type isn't real until the extra digits are demonstrably there, past the point where a double runs out.
The upshot
Quad precision is a specific, bounded tool: ~34 decimal digits, roughly 2⁻¹¹² epsilon, 18 digits of headroom over a double, at the cost of arithmetic that runs several times slower. You reach for it when cancellation, long accumulation, or ill-conditioning would otherwise eat your answer — and you leave it alone the rest of the time, because a well-conditioned double computation doesn't need it. What makes Equana's implementation worth trusting is that the precision is real end to end: chosen as double-double for speed, stored as two clean IEEE words, and — most importantly — carried at full width from the literal you type to the bytes that hold it, with verification cells to confirm the digits are really there.
Try it at equana.dev. For how the same value flows through native, WASM, and TypeScript backends unchanged, see the same code, three backends; for another corner of the numerics library where representation choices decide what's possible in a browser tab, see sparse matrices in the browser.