The Floating-Point Fused Multiply-Add Rounding Advantage: Why FMA Isn't Just Faster, It's More Accurate

2026-07-08

You already know FMA computes a*b + c in one instruction with one rounding step. But the deeper story is about numerical accuracy — FMA isn't just a performance optimization, it changes what answers your CPU is capable of producing. Once you see this, you'll understand why entire numerical libraries were rewritten when FMA landed.

The two-rounding problem. Without FMA, a*b + c executes as two separate ops. The multiplier produces a full-width intermediate (e.g., 106 bits for double-precision inputs), then rounds it to 53 bits before the adder ever sees it. The adder then rounds again. Two roundings, two chances to lose bits. FMA keeps the full 106-bit product internally and rounds exactly once, at the very end.

Concrete example: catastrophic cancellation. Compute x² - y² where x = 1.00000001, y = 1.0. Naive form: multiply, multiply, subtract — each multiply rounds, and the subtract cancels most bits, exposing rounding error in the low bits of your answer. Better form: (x-y)*(x+y). Best form with FMA: compute x*x, then fma(-y, y, x*x) — the y*y product stays full-width inside the FMA, so the subtraction sees the exact product, not a rounded one. You recover bits that were literally impossible to compute without FMA.

Real-world use: correctly-rounded division and sqrt. Modern libm and hardware sqrt/division routines use Newton-Raphson iteration. Each iteration looks like x_next = x*(2 - d*x) — an FMA pattern. Without FMA, the iteration's error term grows; with FMA, one iteration converges to correctly-rounded results. This is why AArch64's FSQRT and x86's VDIVPS got dramatically more accurate once FMA was assumed present.

The 2-ULP rule of thumb. For a naive a*b + c without FMA, worst-case error is ~1 ULP from the multiply plus ~1 ULP from the add — call it 2 ULPs. With FMA, worst-case is 0.5 ULP (correctly rounded). That's a 4× accuracy improvement per operation, and it compounds across long dot products or polynomial evaluations.

The catch. FMA changes results. Code that computed a*b + c the old way and code that uses FMA will produce different bits. This is why C99 added #pragma STDC FP_CONTRACT — some numerical code depends on the old two-rounding behavior for reproducibility across platforms. GCC's -ffp-contract=fast lets the compiler emit FMA freely; =off forbids it. Choose wrong and your regression tests fail on new hardware.

Key Takeaway: FMA's single rounding step doesn't just save a cycle — it cuts worst-case error from 2 ULPs to 0.5 ULP, enabling entire classes of algorithms (correctly-rounded division, catastrophic-cancellation recovery) that naive multiply-then-add cannot achieve.

All newsletters