2026-07-02
IEEE 754 defines five exceptions: invalid operation, division by zero, overflow, underflow, and inexact. Every FP instruction potentially raises one or more of these, and the CPU has to detect them, update sticky flag bits in the FPU status register (MXCSR on x86, FPSR on ARM), and — if the exception is unmasked — deliver a synchronous trap. All of this happens on the critical path of every FPU op.
The hardware handles this by running exception detection in parallel with the actual math. The multiplier computes the product while a separate block inspects operand exponents for overflow risk and checks for NaN/Inf inputs. If either operand is NaN, the result is a quiet NaN (qNaN) with a specific payload — the hardware muxes past the actual arithmetic and forwards a canonical qNaN, which is why NaN propagation is "fast" in the arithmetic sense.
The expensive case is when the fast path can't handle the operation and the CPU falls back to microcode assist. On Intel, this includes: denormal inputs or outputs, unmasked exceptions actually firing, and certain rounding-mode corner cases. A microcode assist is essentially a trap into a tiny internal handler that drains the pipeline, executes a sequence of internal uops, and resumes — costing hundreds of cycles versus the 4-cycle happy path.
Concrete example: A physics simulation with a runaway integration produces NaN in one particle's position. On the next frame, that NaN gets multiplied into the transformation matrix. Every matrix multiply now involves NaN, and while each individual op stays fast, the NaN spreads — within a few frames, thousands of downstream values are NaN. Worse, if the code path then hits a comparison (`if (x > threshold)`), IEEE 754 says NaN compares false to everything, silently taking the wrong branch. The performance stays the same but the program is now computing garbage at full speed.
The truly nasty case: a numerical solver drifts into denormal range (values below ~1.2e-38 for float32). Each denormal op triggers a microcode assist. A tight loop that ran at 4 GFLOPS suddenly runs at 40 MFLOPS — a 100× slowdown with no code change and no visible symptom except the frame rate collapsing.
Rule of thumb: If your FP-heavy code mysteriously slows down over time, set FTZ (flush-to-zero) and DAZ (denormals-are-zero) in MXCSR. This tells the FPU to treat denormals as zero, skipping the assist entirely. On x86: _mm_setcsr(_mm_getcsr() | 0x8040). Cost: slightly less accuracy near zero. Benefit: no random 100× cliffs.
