<cfenv>
C++11 header for IEEE 754 floating-point environment control — exception flags, rounding modes, and environment save/restore.
<cfenv>since C++11A C++ wrapper around C99's <fenv.h> that exposes IEEE 754 floating-point exception flags, rounding mode control, and full environment save/restore via types, macros, and functions placed into namespace std.
Overview
Modern hardware implementing IEEE 754 tracks five exception conditions as sticky status flags: invalid operation, division by zero, overflow, underflow, and inexact result. <cfenv> lets you inspect and manipulate these flags programmatically, switch the active rounding mode at runtime, and checkpoint the entire floating-point environment so library code does not leak state changes to its callers.
Compiler cooperation is required. Under the C++ as-if rule, compilers may reorder or eliminate floating-point operations freely. To make FP state visible as observable side effects, you must place #pragma STDC FENV_ACCESS ON before any code that reads or writes the FP environment. Without it, an optimizing compiler is permitted — and in practice often does — eliminate flag-setting operations entirely. GCC additionally requires -frounding-math when dynamically changing the rounding mode; Clang requires -frounding-math or -fno-unsafe-math-optimizations for the same reason.
All identifiers are in namespace std. The unqualified C names from <fenv.h> may also be visible, but should be treated as implementation details.
Exception flags are sticky. Once an IEEE 754 exception occurs, its flag remains set until explicitly cleared. Flags do not trap by default in standard C++. POSIX extends the interface with feenableexcept / fedisableexcept to install trap handlers, but those are non-standard and absent on Windows and many embedded targets.
Syntax
#include <cfenv>
// Opaque types
std::fenv_t // complete floating-point environment state
std::fexcept_t // exception flag state (a subset of fenv_t)
// Exception flag macros (a platform may define any subset; unsupported ones == 0)
FE_DIVBYZERO // x / 0.0 where x is finite and non-zero
FE_INEXACT // result is not exactly representable
FE_INVALID // e.g. 0.0/0.0, sqrt(-1.0), NaN comparisons
FE_OVERFLOW // result magnitude exceeds the representable range
FE_UNDERFLOW // result magnitude is too small (subnormal or flushed to zero)
FE_ALL_EXCEPT // bitwise OR of all supported exception macros
// Rounding mode macros
FE_DOWNWARD // toward -∞
FE_TONEAREST // round-half-to-even (IEEE 754 default, banker's rounding)
FE_TOWARDZERO // toward zero (truncation)
FE_UPWARD // toward +∞
FE_DFL_ENV // pointer to const fenv_t representing the default environment
// Exception flag functions
int std::feclearexcept(int excepts); // clear flags
int std::feraiseexcept(int excepts); // raise flags
int std::fetestexcept(int excepts); // test flags (returns set subset)
int std::fegetexceptflag(std::fexcept_t* flagp, int excepts); // snapshot flags
int std::fesetexceptflag(const std::fexcept_t* flagp, int excepts); // restore snapshot
// Rounding mode
int std::fegetround(); // returns current rounding mode macro, or -1
int std::fesetround(int round); // 0 on success
// Full environment save/restore
int std::fegetenv(std::fenv_t* envp); // snapshot env (flags + rounding + more)
int std::fesetenv(const std::fenv_t* envp); // restore env verbatim
int std::feholdexcept(std::fenv_t* envp); // snapshot, clear all flags, non-stop mode
int std::feupdateenv(const std::fenv_t* envp); // restore env; re-raise pre-existing exceptionsEvery function returns zero on success and non-zero on failure. Failure typically means the operation is unsupported on the current platform.
Examples
Detecting exceptions from a numeric computation
#pragma STDC FENV_ACCESS ON
#include <cfenv>
#include <cmath>
#include <cstdio>
// math_errhandling & MATH_ERREXCEPT must be non-zero for <cmath>
// functions to actually raise FP exception flags (true on most hosted platforms).
void probe(double x) {
std::feclearexcept(FE_ALL_EXCEPT); // flags are sticky — clear before the region
double r = std::log(x);
if (std::fetestexcept(FE_INVALID)) { std::puts("invalid: argument < 0"); return; }
if (std::fetestexcept(FE_DIVBYZERO)) { std::puts("pole: log(0) = -inf"); return; }
if (std::fetestexcept(FE_OVERFLOW)) { std::puts("overflow"); return; }
std::printf("log(%g) = %.17g (inexact=%d)\n",
x, r, !!(std::fetestexcept(FE_INEXACT)));
}
int main() {
probe(-1.0); // FE_INVALID
probe(0.0); // FE_DIVBYZERO
probe(1.0); // exact; FE_INEXACT not set
probe(1e300); // FE_INEXACT
}Directed rounding for interval arithmetic
Switching rounding mode before each operation guarantees conservative (outer) bounds. This is the foundation of validated numerics.
#pragma STDC FENV_ACCESS ON
#include <cfenv>
#include <cstdio>
struct Interval { double lo, hi; };
Interval iadd(Interval a, Interval b) {
double lo, hi;
std::fesetround(FE_DOWNWARD); // C++11 — round toward -∞ for lower bound
lo = a.lo + b.lo;
std::fesetround(FE_UPWARD); // C++11 — round toward +∞ for upper bound
hi = a.hi + b.hi;
std::fesetround(FE_TONEAREST); // always restore default before returning
return {lo, hi};
}
Interval imul(Interval a, Interval b) {
// For positive intervals only (full case requires checking all four products)
double lo, hi;
std::fesetround(FE_DOWNWARD); lo = a.lo * b.lo;
std::fesetround(FE_UPWARD); hi = a.hi * b.hi;
std::fesetround(FE_TONEAREST);
return {lo, hi};
}
int main() {
// 0.1 + 0.2 is not exactly representable; the interval must contain 0.3
Interval x{0.1, 0.1}, y{0.2, 0.2};
auto z = iadd(x, y);
std::printf("[%.17g, %.17g]\n", z.lo, z.hi);
// [0.29999999999999998, 0.30000000000000004] — contains 0.3
}RAII guard for exception-transparent library code
Library functions that modify the FP environment must restore it on exit. The idiomatic approach is a RAII guard built on feholdexcept / feupdateenv.
#pragma STDC FENV_ACCESS ON
#include <cfenv>
#include <cmath>
#include <stdexcept>
class FenvGuard {
std::fenv_t saved_;
public:
FenvGuard() { std::feholdexcept(&saved_); } // save, clear all flags, non-stop mode
~FenvGuard() { std::feupdateenv(&saved_); } // restore; re-raise pre-existing exceptions
FenvGuard(const FenvGuard&) = delete;
FenvGuard& operator=(const FenvGuard&) = delete;
};
// Caller's FP environment is fully preserved regardless of what happens inside.
double hypot_safe(double a, double b) {
FenvGuard guard;
std::fesetround(FE_TONEAREST);
double r = std::sqrt(a * a + b * b);
if (std::fetestexcept(FE_OVERFLOW)) {
// Handle internally before guard destructor restores caller's state
r = std::numeric_limits<double>::infinity();
}
return r;
}feholdexcept differs critically from fegetenv: it also clears all exception flags and installs a non-stop mode so that FP exceptions inside the guarded block do not propagate. feupdateenv differs from fesetenv: it restores the saved environment and then re-raises any exceptions that had been pending in the saved state before our code ran — preserving the caller's pre-existing flags.
Exception flags as computation audit
Wrapping a numeric block between feclearexcept and fetestexcept turns the FP exception flags into an audit log. Any exceptional condition that occurred anywhere inside the block — even deep inside a called function — is recorded in the flags and can be inspected afterward. This is especially useful in test suites for numerical algorithms where you want to assert that a result was computed cleanly.
#pragma STDC FENV_ACCESS ON
#include <cfenv>
#include <cmath>
#include <cassert>
#include <cstdio>
// Returns true if the computation was free of all five exceptional conditions.
bool audit_computation(double* result, double x) {
std::feclearexcept(FE_ALL_EXCEPT); // C++11 — wipe slate before region
*result = std::log1p(x) * std::sqrt(x) / (x + 1.0);
int raised = std::fetestexcept(FE_ALL_EXCEPT);
if (raised) {
if (raised & FE_INVALID) std::puts(" audit: FE_INVALID raised");
if (raised & FE_DIVBYZERO) std::puts(" audit: FE_DIVBYZERO raised");
if (raised & FE_OVERFLOW) std::puts(" audit: FE_OVERFLOW raised");
if (raised & FE_UNDERFLOW) std::puts(" audit: FE_UNDERFLOW raised");
if (raised & FE_INEXACT) std::puts(" audit: FE_INEXACT raised");
}
return raised == 0;
}
// In a test suite:
void test_well_behaved_input() {
double result;
bool clean = audit_computation(&result, 1.0);
// FE_INEXACT is expected for most transcendental results; mask it if acceptable
int raised = std::fetestexcept(FE_ALL_EXCEPT & ~FE_INEXACT);
assert(raised == 0 && "computation raised unexpected FP exception");
}The audit pattern catches silent errors that would otherwise propagate as NaN or infinity through an entire computation chain before being noticed.
Stochastic rounding / Monte Carlo arithmetic
A classical technique for estimating floating-point error bounds is Monte Carlo Arithmetic (MCA), also known as CESTAC (Contrôle et Estimation Stochastique des Arrondis de Calcul). The idea is simple: run the same computation twice — once with directed rounding toward +∞ and once toward -∞ — and treat the spread between the two results as a conservative lower bound on the rounding error accumulated over the entire computation.
#pragma STDC FENV_ACCESS ON
#include <cfenv>
#include <cmath>
#include <cstdio>
#include <vector>
#include <numeric>
// Sum 1000 doubles under FE_UPWARD, then FE_DOWNWARD.
// The difference is a lower-bound estimate of the accumulated rounding error.
double directed_sum(const std::vector<double>& v, int rounding_mode) {
std::fesetround(rounding_mode); // C++11
double acc = 0.0;
for (double x : v) {
acc += x; // each addition rounds in the directed direction
}
std::fesetround(FE_TONEAREST); // restore default before returning
return acc;
}
int main() {
// Construct a vector where catastrophic cancellation is likely:
// alternating large positive and negative values whose exact sum is small.
std::vector<double> data;
data.reserve(1000);
for (int i = 0; i < 500; ++i) {
data.push_back( 1e15 + i); // large positive
data.push_back(-1e15 - i); // large negative (exact sum of each pair = 0)
}
// Add a small meaningful signal at the end
data.push_back(42.0);
double sum_up = directed_sum(data, FE_UPWARD); // C++11
double sum_down = directed_sum(data, FE_DOWNWARD); // C++11
double spread = std::abs(sum_up - sum_down);
std::printf("FE_UPWARD result: %.17g\n", sum_up);
std::printf("FE_DOWNWARD result: %.17g\n", sum_down);
std::printf("Spread (error estimate): %.6g\n", spread);
std::printf("True answer: 42.0\n");
// If spread >> 1 ULP, the summation is numerically unstable for this input.
}The spread between the two directed-rounding results reveals how much rounding error the algorithm accumulated. A spread much larger than one ULP of the result signals numerical instability. For the example above, the alternating cancellation causes the spread to be non-trivial even though the mathematical answer is exactly 42. Full CESTAC uses random rounding-mode selection (stochastic arithmetic) to derive a statistical confidence interval; the two-pass directed version shown here is a deterministic approximation.
Kahan compensated summation
Naive summation of n doubles accumulates O(n) rounding error: each addition can lose up to ½ ULP, and over n additions those losses add up. Kahan's algorithm maintains a running compensation term that captures the lost low-order bits and feeds them back into the next addition, reducing the error to O(1) — independent of n — at the cost of four floating-point operations per element instead of one.
#pragma STDC FENV_ACCESS ON
#include <cfenv>
#include <cstdio>
#include <vector>
// Kahan compensated summation — O(1) rounding error regardless of n. C++11
double kahan_sum(const std::vector<double>& v) {
double sum = 0.0;
double comp = 0.0; // running compensation for lost low-order bits
for (double x : v) {
double y = x - comp; // compensated addend: correct for previous loss
double t = sum + y; // sum + y: low-order bits of y are lost here
comp = (t - sum) - y; // recover the lost bits (should be ~0 in exact arith)
sum = t;
}
return sum;
}
// Naive sum for comparison
double naive_sum(const std::vector<double>& v) {
double acc = 0.0;
for (double x : v) acc += x;
return acc;
}
int main() {
// 10^7 additions of 0.1: exact answer is 1,000,000.0
std::vector<double> data(10'000'000, 0.1);
double naive = naive_sum(data);
double kahan = kahan_sum(data);
std::printf("Naive: %.17g (error: %.6g)\n", naive, naive - 1e7 * 0.1);
std::printf("Kahan: %.17g (error: %.6g)\n", kahan, kahan - 1e7 * 0.1);
// Naive error grows proportionally with n; Kahan error is bounded.
}There is a critical interaction with compiler optimizations. The variable comp = (t - sum) - y relies on the fact that (t - sum) is evaluated before subtracting y. With -ffast-math or -O3 on GCC/Clang without -frounding-math, the compiler is permitted to algebraically simplify (t - sum) - y to t - sum - y, which it may then reduce to zero — completely eliminating the compensation. The #pragma STDC FENV_ACCESS ON directive is intended to block such rewrites, but its interaction with -ffast-math is implementation-defined. The safest approach is to compile compensation-sensitive code with -fno-unsafe-math-optimizations or, on GCC, to mark the function with __attribute__((optimize("no-unsafe-math-optimizations"))).
FP_CONTRACT pragma
The C++ standard provides #pragma STDC FP_CONTRACT ON/OFF/DEFAULT to control whether the compiler may fuse a multiply-add sequence a*b + c into a single fused multiply-add (FMA) instruction. An FMA computes (a*b) + c in a single step with only one final rounding, which is both faster and more accurate than two separate instructions each with their own rounding. However, the FMA result differs from the two-instruction sequence at the last significant bit, breaking strict IEEE 754 bit-for-bit reproducibility.
// Example: when FMA contraction is desirable — better accuracy, one rounding. C++11
#include <cmath>
double dot_product_fast(const double* a, const double* b, int n) {
// With FP_CONTRACT ON (the default), the compiler may emit FMA instructions.
// This is almost always what you want: faster and more accurate.
#pragma STDC FP_CONTRACT ON
double sum = 0.0;
for (int i = 0; i < n; ++i)
sum += a[i] * b[i]; // may become: sum = fma(a[i], b[i], sum)
return sum;
}
// Example: when FMA contraction must be disabled — exact IEEE 754 reproducibility.
double dot_product_reproducible(const double* a, const double* b, int n) {
// With FP_CONTRACT OFF, every multiply and every add is a distinct
// IEEE 754 operation with its own rounding. Results are identical
// across compilers, architectures, and optimization levels.
#pragma STDC FP_CONTRACT OFF
double sum = 0.0;
for (int i = 0; i < n; ++i)
sum += a[i] * b[i];
return sum;
}The corresponding compiler flags give you coarser-grained control at the translation-unit level. On Clang, -ffp-contract=fast enables FMA contraction across statements (even when the multiply and add are on separate lines), while -ffp-contract=off disables it entirely. GCC contracts within a single expression by default; -ffp-contract=fast extends contraction across statements. When writing numerical code that must produce identical bit patterns on different machines or compiler versions — for instance, a checksum, a physics simulation with lockstep replay, or a financial calculation under audit — use FP_CONTRACT OFF and the strict compiler flags shown in the table below.
POSIX feenableexcept for debugging
On Linux and other POSIX systems, feenableexcept upgrades the five silent IEEE 754 exception flags into hardware trap signals. When a trapped exception occurs, the kernel delivers SIGFPE to the process, immediately revealing the exact location of the problematic operation via the signal handler's stack trace. This converts silent NaN propagation — which can corrupt results silently across thousands of subsequent operations — into a fatal crash at the precise source instruction.
// feenableexcept is POSIX/Linux only — guard all uses. C++11
#ifdef __linux__
#include <cfenv>
#include <csignal>
#include <cstdio>
#include <cstdlib>
#include <execinfo.h> // GNU extension for backtrace()
// SIGFPE handler: print a stack trace, then abort.
static void fp_exception_handler(int /*sig*/, siginfo_t* info, void* /*ctx*/) {
std::puts("\n*** Floating-point exception detected ***");
// Decode which exception triggered the signal (si_code)
switch (info->si_code) {
case FPE_FLTDIV: std::puts(" cause: division by zero"); break;
case FPE_FLTINV: std::puts(" cause: invalid operation"); break;
case FPE_FLTOVF: std::puts(" cause: overflow"); break;
case FPE_FLTUND: std::puts(" cause: underflow"); break;
case FPE_FLTRES: std::puts(" cause: inexact result"); break;
default: std::puts(" cause: unknown FP exception"); break;
}
// Print a raw stack trace (requires linking with -rdynamic for symbol names)
void* frames[64];
int depth = backtrace(frames, 64);
backtrace_symbols_fd(frames, depth, STDERR_FILENO);
std::abort();
}
// Call once at program startup (in main, before any numeric work).
void install_fp_traps() {
struct sigaction sa{};
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = fp_exception_handler;
sigemptyset(&sa.sa_mask);
sigaction(SIGFPE, &sa, nullptr);
// Enable traps for the three most dangerous silent exceptions.
// FE_INEXACT fires on almost every FP operation — do not enable it here.
feenableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW);
}
int main() {
install_fp_traps();
double x = 0.0;
double y = 1.0 / x; // <- SIGFPE delivered here; handler prints backtrace
(void)y;
return 0;
}
#endif // __linux__The SA_SIGINFO flag is essential: it makes the kernel pass a siginfo_t to the handler, which contains si_code identifying which of the five IEEE 754 exceptions fired. Without SA_SIGINFO you receive only the signal number and cannot distinguish a division-by-zero from an invalid operation. Note that feenableexcept is orthogonal to the standard exception flags: the flags accumulate silently even when traps are disabled; traps make the exception immediately fatal rather than letting it propagate silently.
Best Practices
Always use feholdexcept + feupdateenv in pairs. Using fesetenv to restore after fegetenv silently discards both the pre-existing exception flags from the caller and any raised inside your function. feholdexcept / feupdateenv is the only standard combination that correctly isolates internal exceptions while preserving the caller's state.
Clear flags immediately before the region under test. Exception flags are sticky and accumulate across all prior operations. Calling feclearexcept(FE_ALL_EXCEPT) at function entry is not sufficient if unrelated operations occur between entry and the region you care about.
Check math_errhandling before relying on exception flags. The macro math_errhandling is a bitfield. If math_errhandling & MATH_ERREXCEPT is zero, <cmath> functions signal errors via errno only, and FP exception flags will not be raised. On such platforms, use errno checking instead.
Restore the rounding mode before every return path. If a function can throw or return early, restore FE_TONEAREST on every path. The RAII guard above handles this automatically.
Prefer #pragma STDC FP_CONTRACT OFF over compiler flags for reproducibility-sensitive functions. Pragma-level control is narrower and safer than translation-unit-wide flags: you can isolate the reproducibility requirement to exactly the function that needs it without penalizing the rest of the translation unit.
Use feenableexcept traps in debug builds only. Hardware FP traps impose a context-switch overhead on every exception and change the observable behavior of the program. Enable them during development and testing to catch silent NaN propagation early; disable them (or #ifdef them out) in production builds.
Common Pitfalls
Omitting #pragma STDC FENV_ACCESS ON. Without this pragma, the implementation is allowed to treat FP flags as dead stores and optimize them away. With -O2 or higher, GCC and Clang will do exactly this in common cases. Support for the pragma varies: GCC respects it as of GCC 7+; Clang respects it but may emit a warning on older versions.
Confusing fesetenv with feupdateenv. fesetenv is a verbatim replace: it overwrites the environment, including any exception flags the caller had accumulated before the call. feupdateenv re-raises those pre-existing flags after restoring. Mixing them up silently corrupts the caller's exception state.
Assuming all exception macros are non-zero. IEEE 754 support is implementation-defined. On a platform where FE_UNDERFLOW == 0, calling fetestexcept(FE_UNDERFLOW) always returns zero, even if an underflow actually occurred. Portable code must test fetestexcept(excepts) & excepts after verifying the macro is non-zero.
Using feenableexcept / fedisableexcept portably. These POSIX extensions install SIGFPE trap handlers on FP exceptions. They are unavailable on Windows and many embedded toolchains and have no equivalent in ISO C++. Code using them is non-portable and must be conditionally compiled.
Ignoring return values. All fe* functions return non-zero to indicate unsupported operations. On targets without IEEE 754 hardware (certain microcontrollers or soft-float ABIs), most operations silently fail. Ignoring the return value means your code will appear to work correctly in test environments but produce wrong answers silently in production on different hardware.
Letting -ffast-math eliminate Kahan compensation. The comp = (t - sum) - y idiom is algebraically zero in exact arithmetic, so -ffast-math legally simplifies it away. Always compile summation-sensitive code with -fno-unsafe-math-optimizations when Kahan or similar compensation techniques are in use.
Compiler flags reference
The table below lists the flags that control IEEE 754 compliance for the three major C++ compilers. In each row, stricter compliance generally means less aggressive optimization; choose the narrowest flag that satisfies your correctness requirement.
| Concern | GCC | Clang | MSVC |
|---|---|---|---|
| Strict IEEE 754 mode (all of the below) | -fno-unsafe-math-optimizations -frounding-math -fsignaling-nans | -ffp-model=strict | /fp:strict |
| Disable unsafe math rewrites (no reassoc, no fma fusion, etc.) | -fno-unsafe-math-optimizations | -fno-unsafe-math-optimizations | /fp:precise |
| Honor runtime rounding mode changes | -frounding-math | -frounding-math | /fp:strict (implied) |
| Disable FMA contraction across statements | -ffp-contract=off | -ffp-contract=off | /fp:precise (default) |
| Enable FMA contraction across statements | -ffp-contract=fast | -ffp-contract=fast | /fp:fast |
| Use SSE/SSE2 for FP (avoids x87 80-bit excess precision) | -mfpmath=sse -msse2 | default on x86-64 | default on x64 |
Tell compiler FENV_ACCESS is in effect | -frounding-math (implied) | -frounding-math (implied) | /fp:strict (implied) |
| Allow all unsafe optimizations (fastest, least accurate) | -ffast-math | -ffast-math | /fp:fast |
GCC's -ffast-math is a bundle that enables -fno-math-errno, -funsafe-math-optimizations, -ffinite-math-only, -fno-rounding-math, -fno-signaling-nans, -fcx-limited-range, and -fexcess-precision=fast. Clang's -ffast-math enables a similar set. Both are incompatible with any meaningful use of <cfenv>. MSVC's /fp:fast is the equivalent. When profiling reveals that FP math is a bottleneck, prefer narrower flags (-fno-math-errno, -fassociative-math) that give most of the speed without sacrificing rounding-mode or exception-flag correctness.
Embedded platform considerations
On many microcontrollers — ARM Cortex-M0, AVR, most 8-bit and 16-bit DSPs, and any target configured with a soft-float ABI — there is no hardware FPU, or the FPU present does not implement the full IEEE 754 exception and rounding-mode infrastructure. The fesetround function may return non-zero (failure) without changing anything. math_errhandling may be 0, meaning neither errno nor exception flags are set by math functions. FE_ALL_EXCEPT may expand to 0.
The best practice on embedded targets is to probe the FP environment at startup and fall back gracefully rather than assuming capabilities.
#pragma STDC FENV_ACCESS ON
#include <cfenv>
#include <cstdio>
struct FpCapabilities {
bool rounding_modes; // fesetround actually works
bool exception_flags; // fetestexcept actually works
bool math_errexcept; // <cmath> raises exception flags
};
// Probe FP capabilities at startup. C++11
FpCapabilities probe_fp_capabilities() {
FpCapabilities caps{};
// Test rounding mode support: try to set FE_UPWARD and read it back.
if (std::fesetround(FE_UPWARD) == 0) {
caps.rounding_modes = (std::fegetround() == FE_UPWARD);
}
std::fesetround(FE_TONEAREST); // restore default regardless
// Test exception flag support: raise FE_INEXACT manually and test it.
std::feclearexcept(FE_ALL_EXCEPT);
if (std::feraiseexcept(FE_INEXACT) == 0) {
caps.exception_flags = !!(std::fetestexcept(FE_INEXACT));
}
std::feclearexcept(FE_ALL_EXCEPT);
// Test whether <cmath> raises flags (MATH_ERREXCEPT bit).
caps.math_errexcept = !!(math_errhandling & MATH_ERREXCEPT);
return caps;
}
int main() {
FpCapabilities caps = probe_fp_capabilities();
std::printf("Rounding modes: %s\n", caps.rounding_modes ? "yes" : "no (fesetround is a no-op)");
std::printf("Exception flags: %s\n", caps.exception_flags ? "yes" : "no (flags always zero)");
std::printf("math_errexcept: %s\n", caps.math_errexcept ? "yes" : "no (use errno instead)");
if (!caps.rounding_modes) {
// Fall back: use software interval arithmetic or avoid directed rounding.
}
if (!caps.exception_flags) {
// Fall back: check errno after every math call, or use isnan/isinf.
}
}When targeting embedded platforms, avoid unconditional use of fesetround in production paths. If the target returns failure, the rounding mode is unchanged and any interval arithmetic built on top of it silently produces wrong (non-conservative) bounds. The startup probe above should be run once and the result cached; downstream code branches on caps.rounding_modes rather than assuming success.