← All posts ·

Denormal Numbers in Audio DSP: A Developer's Guide

Denormal Numbers in Audio DSP: A Developer's Guide

Hands adjusting digital audio console knobs

Subnormal (denormal) floating-point values are the single most common source of surprise CPU spikes in real-time audio plugins. Per IEEE 754, subnormals are non-zero values smaller than the minimum normalized float, and most x86 and ARM microarchitectures handle them through a slow microcode path that can be orders of magnitude more expensive than normal arithmetic. The fix is usually one of two things: enable Flush-to-Zero (FTZ) and Denormals-Are-Zero (DAZ) in the MXCSR register at plugin startup, or inject a tiny alternating normalized offset (around 1e-18) into every feedback path. Both are inaudible. Neither is optional if you care about deterministic real-time behavior.

Pro Tip: Even if your DAW host enables FTZ globally, never rely on it. A plugin that ships without its own denormal handling is one host setting away from a support ticket.


Key Takeaways

Denormal (subnormal) numbers are the most common hidden cause of real-time CPU spikes in audio plugins, and every feedback path in your DSP code is a potential source.

Point Details
Enable FTZ/DAZ on desktop Set both MXCSR bits at plugin startup; use JUCE's ScopedNoDenormals in processBlock.
Use alternating offset as fallback Add ±1e-18 to feedback accumulators in portable builds where hardware FTZ is unavailable.
Target the feedback path Mitigate inside the accumulator, not at the output; a subnormal at the output means slow arithmetic already ran.
Benchmark before and after Run a decay-generator harness with FTZ on and off; the timing difference is your denormal cost.
Automate in CI Include a denormal micro-benchmark in your CI suite to catch regressions from future refactors.

Table of Contents

What are denormal numbers and how does IEEE 754 define them?

Every single-precision float in IEEE 754 stores a sign bit, an 8-bit exponent, and a 23-bit mantissa. When the exponent field is non-zero, hardware assumes an implicit leading 1 before the mantissa bits, giving you 24 bits of precision for free. That is a normalized value.

The problem starts when a value is too small to fit in the normal exponent range. The smallest positive normalized float is roughly 1.175e-38. Below that, IEEE 754 does not simply round to zero. Instead, it gradually reduces precision by shifting the mantissa right and setting the exponent field to all zeros, removing the implicit leading one. These are subnormal (also called denormal) values, and they extend representable range down to approximately 1.4e-45 at the cost of progressively fewer significant bits.

W. Kahan's IEEE 754 design notes explain the motivation: gradual underflow prevents catastrophic cancellation errors near zero that a hard flush would introduce. That is the right call for general numerical computing. For real-time audio DSP, it is often the wrong trade-off.

MathWorks documents the same precision-versus-range trade-off and notes that simulation environments offer flush-to-zero modes precisely because the performance cost is unacceptable in time-critical loops.


How do denormal numbers arise in audio DSP?

The short answer: any recursive structure that lets a signal decay toward zero without a hard floor. That covers a large fraction of real audio code.

IIR filters are the classic case. A biquad section processing silence still runs its feedback accumulator every sample. Once the input drops away, the internal state decays exponentially. It crosses the normalized floor and enters subnormal territory, where it can ring for thousands of samples. EarLevel Engineering's practical guide describes exactly this pattern: the filter output is inaudible, but the CPU is still working hard on subnormal arithmetic.

Reverb algorithms compound the problem. A Schroeder or FDN reverb has multiple feedback delay lines, each decaying independently. One subnormal in one delay line can propagate into every subsequent processing stage in the same block, a "poisoning" effect described in Laurent de Soras's paper on denormals in signal processing. Treat the feedback path itself as the primary mitigation target, not just the output.

Other common sources:

Pro Tip: When debugging a CPU spike that only appears during silence or after a note release, check your IIR filter states first. Print the raw float values; if you see exponents in the e-40 range, you have found the source.


Why do denormals slow down your CPU so dramatically?

Normal floating-point arithmetic on x86 and ARM runs in dedicated FPU or SIMD hardware. Subnormal operands often cannot use that fast path. On many microarchitectures, the hardware detects a subnormal input and either routes the operation through a slower microcode sequence or traps to a software emulation handler. The result is a dramatic increase in cycles per operation.

Published micro-benchmark results show that subnormal operands can trigger execution paths that are orders of magnitude slower than equivalent operations on normalized values across multiple common microarchitectures. In a real-time audio context, that translates directly to CPU load spikes that the audio thread scheduler cannot absorb.

The real-time symptoms are specific: a plugin that sits at 3–5% CPU during normal playback suddenly jumps to 40–60% during a reverb tail or after a note release. The audio thread misses its deadline. The host reports a dropout. Users file bug reports about "glitches when notes stop." The root cause is almost never found without knowing to look for subnormals.

ADC 2024 speaker Attila Haraszti confirmed that even on modern processors, subnormals continue to produce real-time CPU spikes, and that enabling FTZ/DAZ remains the standard practical choice for desktop audio plugins. Modern CPUs have improved their subnormal handling, but "improved" does not mean "free."

SSE/AVX code paths are particularly susceptible because SIMD lanes process multiple samples simultaneously. One subnormal in a lane can stall the entire vector operation.


How do you control denormal behavior at the platform and compiler level?

On x86, the MXCSR register controls SSE/AVX floating-point behavior. Two bits matter here:

Setting both gives you the strongest protection. The bbx_audio documentation recommends flush-to-zero semantics for production audio and provides cross-platform utilities for enabling FTZ/DAZ on x86/x86_64.

A minimal C++ snippet to enable both at plugin startup:

#include <pmmintrin.h>
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
_MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON);

On ARM/NEON, the equivalent is the FZ bit in the FPSCR register (AArch32) or the FZ bit in FPCR (AArch64). ARM's flush-to-zero behavior is architecturally defined and generally available, but the API differs by platform and OS. iOS and Android both expose it, though you should verify behavior on each target.

Pro Tip: Never set FTZ/DAZ globally in a shared library without restoring the previous MXCSR state. A plugin that permanently alters the host's FPU flags can break other plugins in the same process. Use RAII wrappers or save/restore the register explicitly.

Laurent de Soras's paper emphasizes that there is no single universal fix. FTZ/DAZ is the right choice for most desktop audio plugins, but portable builds targeting WebAssembly or embedded targets need a different strategy.


How do you detect denormals and reproduce the slowdown in a benchmark?

Detection starts with inspecting float values directly. A subnormal single-precision float has an exponent field of all zeros and a non-zero mantissa. In C++:

bool isDenormal(float x) {
    return ((*reinterpret_cast<uint32_t*>(&x)) & 0x7F800000) == 0 && x != 0.0f;
}

For production code, a branchless flush is more useful than detection alone. But during development, logging the raw hex of filter state variables after a note-off event will immediately reveal whether subnormals are present.

Micro-benchmark pattern: isolate a single IIR biquad, feed it a short burst of signal, then process silence for 10,000 samples. Measure cycles per sample during the decay tail with and without FTZ/DAZ enabled. The difference is your denormal cost.

Benchmark type What to measure Expected signal
IIR biquad decay loop Cycles/sample during tail Large spike without FTZ
FDN reverb silence processing Wall-clock time per buffer 5–20x slower without FTZ
Chained biquad filter bank CPU% average Gradual climb, then plateau
Expander release tail Thread scheduling jitter Missed deadlines at low buffer sizes

Common false positives: background OS scheduler activity, cache misses on first buffer, and thermal throttling. Run benchmarks with CPU affinity pinned to a single core, disable turbo boost for consistency, and average over at least 1,000 buffer iterations before drawing conclusions.


Concrete mitigation patterns you can apply today

The choice of mitigation depends on your architecture and portability requirements. Laurent de Soras's paper lays out the trade-off space clearly: there is no single approach that works everywhere, and treating denormal prevention as a design requirement rather than an afterthought is the right frame.

FTZ/DAZ (preferred for desktop x86/x86_64):

// JUCE RAII wrapper — place at top of processBlock
juce::ScopedNoDenormals noDenormals;

This is the lowest-overhead solution when available. The cost is near zero; the protection is complete for SSE/AVX paths.

Tiny alternating offset (portable fallback):

Add a small normalized constant to the feedback accumulator. EarLevel Engineering recommends a value around 1e-18 (approximately -360 dBFS), which is well below the threshold of human hearing. The alternating-sign trick prevents DC blockers from removing it:

static float dcOffset = 1e-18f;
dcOffset = -dcOffset;
filterState += dcOffset;

Flip the sign every buffer, not every sample, to keep the overhead minimal.

Conditional flush (branchless):

float flushDenormal(float x) {
    return ((*reinterpret_cast<uint32_t*>(&x)) & 0x7F800000) ? x : 0.0f;
}

Useful in portable builds where hardware FTZ is unavailable, but carries a per-sample branch cost.

Algorithm redesign:

Pro Tip: The alternating-sign offset is your best portable fallback, but document it clearly in the code. Future developers who see filterState += 1e-18f without a comment will remove it as "dead code" during cleanup.


Concrete mitigation patterns you can apply today — overview diagram

Numerical accuracy trade-offs and best practices for production audio

Flushing subnormals to zero is audibly transparent in almost every real-world audio scenario. Values below roughly -300 dBFS are inaudible by any practical measure. The precision loss from flushing is real in a mathematical sense but irrelevant to the listener.

That said, a few cases warrant care:

Production checklist:

Pro Tip: Cross-platform policy matters. A plugin that ships with FTZ/DAZ on desktop but no fallback on mobile will behave differently on iOS. Test on every target architecture before release, not just the development machine.

Plugin CPU optimization practices at the architecture level reinforce this: denormal handling belongs in the same category as buffer size selection and thread priority, not as an afterthought in the final QA pass.


Empirical micro-benchmark evidence and a reproducible test harness

The academic micro-benchmark paper demonstrates that subnormal operands produce measurably slower execution across multiple common microarchitectures. The slowdown is not uniform: some chips show a 2–5x penalty, others show far larger gaps depending on the operation type and pipeline depth. The consistent finding is that the slow path is real and measurable, not theoretical.

A practical test harness for plugin developers:

  1. Decay generator: initialize a biquad with a short burst of white noise at -20 dBFS, then switch input to silence.
  2. Timing loop: process 10,000 samples in blocks of 512, recording wall-clock time per block using std::chrono::high_resolution_clock.
  3. Two runs: first with FTZ/DAZ disabled, then with FTZ/DAZ enabled. Keep all other conditions identical.
  4. Metric: cycles per sample during the decay tail (samples 512–10,000).
Condition Expected behavior Mitigation applied
FTZ/DAZ off, decaying IIR Slow path active in tail None
FTZ/DAZ on, decaying IIR Fast path throughout FTZ/DAZ
Alternating offset, no FTZ Fast path throughout ±1e-18 offset
Leaky integrator, no FTZ Fast path throughout Algorithm redesign

Run at 48 kHz, 512-sample buffer, with CPU affinity pinned and turbo boost disabled. Average over 100 iterations. The difference between the first and any of the mitigated rows should be clearly visible in wall-clock time.

ADC 2024 confirmed that these patterns remain relevant on current hardware. The numbers shift between microarchitecture generations, but the qualitative result holds: subnormals cost more than normalized values, and the cost is large enough to cause real-time failures.

Pro Tip: Automate this harness in CI. A future refactor that accidentally removes a ScopedNoDenormals call will show up immediately as a regression in the timing numbers, before it ships.


Empirical micro-benchmark evidence and a reproducible test harness — overview diagram

How Vector-dsp approaches denormal handling in production plugins

At Vector-dsp, denormal handling is not a late-stage optimization. It is a design requirement that goes into every IIR-based processing block from the first prototype.

The default engineering policy: FTZ/DAZ enabled via RAII wrapper in all desktop builds (Windows and macOS, VST3/AU/AAX), with the alternating-sign offset applied in every feedback accumulator as a belt-and-suspenders fallback. Portable builds targeting environments without hardware FTZ fall back to the offset approach exclusively, with the policy documented in the build configuration.

Every release includes a CI micro-benchmark that runs the decay-generator harness described above. A regression in the timing numbers blocks the build. That is the only reliable way to catch a denormal bug introduced by a refactor, because the symptom (a CPU spike during silence) is easy to miss in manual testing.

The broader principle: a plugin that behaves differently at the end of a reverb tail than at the start is not a stable product. Real-time determinism is part of the contract with the host and the user.


Sources

These are the primary references for deeper study on subnormal numbers in audio DSP:

Recommended