r/wg21
P3899R1 - Clarify the behavior of floating-point overflow WG21
Posted by u/fp_ub_watcher_2019 · 6 hr. ago

Document: P3899R1

Authors: Jan Schultke, Matthias Kretz

Date: 2026-02-20

Audience: SG6 (Numerics), EWG (Evolution), CWG (Core Wording)

For roughly a decade, the C++ standard has been technically ambiguous about whether FLT_MAX * 2.0f is undefined behavior on a platform with IEEE 754 infinity. CWG issue 2168 (2016) asked SG6 for guidance; SG6 said overflow to infinity should be well-defined at runtime. Then CWG issue 2723 silently added a “range of representable values” definition that — without consulting SG6 — effectively made overflow UB again. The result: three major compilers implement three different behaviors, especially for constant evaluation, and the standard cannot arbitrate between them.

P3899R1 draws the lines cleanly. On IEEE 754 types: overflow to ±∞ is well-defined at runtime but disqualifies a constant expression. Infinity and quiet NaN propagationinfinity() + 1, nan * 2 — is well-defined and valid in constexpr. Producing NaN from finite operands (indeterminate forms like inf * 0) is not a constant expression. Division by zero remains undefined behavior; the scope of this paper stops short of the full IEEE 754 exceptions story. Underflow is clarified as never having been undefined.

The wording changes touch three places: [expr.pre] ¶4 (rewritten to a two-condition formulation), [basic.fundamental] ¶13 (small parenthetical about infinity), and [expr.const] ¶10 (three new disqualifying conditions). GCC 15 already implements the proposed behavior exactly. Clang and MSVC deviate only on the constexpr overflow case.

If you write numerical code, care about -ffinite-math-only vs. standard conformance, or have ever wondered whether std::numeric_limits<float>::infinity() * 2.0f is valid in a constexpr context — this paper is for you.

▲ 312 points (91% upvoted) · 38 comments
sorted by: best
u/committee_watcher_9000 89 points 5 hr. ago

committee gonna committee. floating point has had this exact ambiguity since at least Kona 2016 and it only took one contradictory DR and three diverging compiler implementations to get a cleanup paper. progress.

u/just_use_rust_they_said 231 points 5 hr. ago

lol Rust doesn’t have floating-point undefined behavior. just saying.

u/rust_is_fine_but 67 points 5 hr. ago

Rust f32/f64 also don’t give you std::fexcept / floating-point exception flags, and the SIMD story is nowhere near std::simd. Different tradeoffs.

u/r_cpp_janitor 1 point 4 hr. agoModerator

Rule 3. Stay on topic.

u/ffast_math_enjoyer 156 points 4 hr. ago

-ffast-math assumes no infinities, no NaNs, no signed zeros. This paper is for people who need IEEE conformance without the flag — HFT desks, numerics researchers, anyone who ships to an auditor. The rest of us just flip the flag and move on.

u/standards_matter_actually 22 points 4 hr. ago

That’s… the point. When you don’t use -ffast-math, the standard should clearly say what your code means. Before this paper it didn’t, not for overflow.

u/practical_dev_throwaway 78 points 4 hr. ago

Eight years of writing C++ and the mental overhead of tracking which FP operations are UB vs. well-defined on which platform is genuinely exhausting. Glad this is being fixed. I still wish there was a [[strict_ieee]] attribute I could slap on a TU and stop thinking about it.

u/actually_reads_cwg_issues 43 points 4 hr. ago

I’ve been tracking CWG2168 since the 2016 Kona meeting. What this paper doesn’t advertise loudly enough in the abstract is that it’s reversing part of the SG6 guidance from that session. The 2016 guidance said overflow to infinity should be well-defined at runtime, but infinity should not be usable as a subexpression in constant expressions — the idea being that constexpr float x = infinity() + 1; was meant to be ill-formed. This paper flips that: infinity propagation is now explicitly valid in constexpr. The justification is reasonable (all three compilers already accept it and rejecting it would break code), but it’s a policy change dressed as a clarification.

The other thing that jumps out: division by zero stays undefined. So on an IEEE 754 type, FLT_MAX * 2 is now well-defined (produces +inf), while 1.0f / 0.0f is still UB. The paper explains the reasons — signed infinity, negative zero, NaN payloads, non-IEEE types — but it means you can’t read this as “C++ finally matches IEEE 754.” Half the table is set.

u/overflow_survivor 28 points 3 hr. ago

The CWG2723 situation is its own story. Someone adds a “range of representable values” definition without looping in SG6, it silently contradicts a decade-old guidance, and a separate paper has to untangle it. Standard archaeology is genuinely a full-time job.

u/gcc_constexpr_implementer 15 points 3 hr. ago

I work on GCC’s constexpr evaluator. The reversal on constexpr infinity propagation is the right call regardless of what SG6 said in 2016. We’ve been accepting infinity() + 1 in constexpr for years — rejecting it now would break existing code. The 2016 concern was about whether constant folding across TUs could produce surprising results; those concerns apply less given how the constexpr evaluation model has matured.

u/embedded_ieee754_pedant 19 points 3 hr. ago

The 2016 concern was deeper than TU-level folding. If you allow infinity as a constant subexpression, you need to decide what inf * 0 means in constexpr: (a) ill-formed, (b) NaN, or (c) UB. This paper picks (a) — inf * 0 is not a constant expression because the result is “not mathematically defined in the domain of real number arithmetic.” Defensible. But it creates an asymmetry: inf * 2 is constexpr-valid, inf * 0 is not. You need to internalize which operations on infinity are “mathematically defined” to predict what constant-evaluates.

u/gcc_constexpr_implementer 11 points 2 hr. ago

The implementation heuristic is: did we produce NaN, and was any input already NaN? Yes → propagation → constexpr OK. No NaN input but NaN output → indeterminate form → not constexpr. “Not mathematically defined in the domain of real number arithmetic” operationalizes to exactly that check. Clear for implementers. Less obviously clear in the standard text, which is the legitimate concern.

u/embedded_ieee754_pedant 8 points 2 hr. ago

The implementation heuristic is sound. What I’d want before CWG wording review is either a forward reference to IEEE 60559 to ground “domain of real number arithmetic,” or a note with worked examples. The phrase is doing load-bearing semantic work — it distinguishes inf * 0 (excluded) from nan * 2 (allowed) — but it’s new vocabulary with no anchor in the standard. Future DR work will hit this phrase and have to reverse-engineer the intent.

u/gcc_constexpr_implementer 6 points 1 hr. ago

Fair point. “Domain of real number arithmetic” should get a footnote or an IEEE 60559 cross-reference before CWG merges the wording. The scope was already ambitious. Worth flagging as an NB comment.

u/hft_latency_monitor 29 points 3 hr. ago

In HFT we sometimes want infinity production as a sentinel — client sends a degenerate price, arithmetic propagates to inf, downstream logic catches it in a single branch instead of bounds-checking every intermediate. The “technically UB” status meant that was load-bearing on implementation-defined behavior even though every IEEE 754 compiler does the right thing. This clarification removes a sword of Damocles. Division by zero staying UB is fine; we already defensively handle denominators.

u/gcc15_early_adopter 45 points 3 hr. ago

GCC 15 already implements exactly this behavior. Clang and MSVC deviate only in the constexpr overflow case — they accept FLT_MAX * 2.0f as a constant expression; this paper would reject it. This is basically standardizing existing practice. Rubber stamp.

u/actually_reads_cwg_issues 18 points 3 hr. ago

That’s how most core wording changes work. Compilers converge on sensible behavior, then a paper makes it normative. The alternative is leaving the standard wrong while implementations move on. Both outcomes are bad; one of them produces useful papers.

u/clang_user_throwaway 7 points 2 hr. ago

What specifically does Clang need to change?

u/gcc_constexpr_implementer 5 points 2 hr. ago

Clang currently accepts FLT_MAX * 2.0f in constexpr contexts — it constant-folds the overflow to infinity without an error. Under this proposal, overflow at constant-evaluation time becomes a new disqualifying condition. Minor implementation change, probably one check in the constexpr evaluator.

u/constexpr_hater_2023 31 points 3 hr. ago

great, constexpr floating point with NaN propagation. can’t wait for the “why is my constant evaluation 3x slower” issue reports in six months

u/pragmatic_constexpr_dev 18 points 3 hr. ago

constexpr FP has been a thing since C++23 (<cmath> constexpr). This paper clarifies what’s legal; it doesn’t add new FP operations to constant evaluation. Your compile times are already paying the cost.

u/just_use_fixed_point -12 points 3 hr. ago

hot take: if your safety-critical code uses floating-point at all you already have bigger problems than FP UB. fixed-point arithmetic with explicit precision tracking is the correct approach for anything where correctness actually matters.

u/hft_latency_monitor 34 points 3 hr. ago

Fixed-point doesn’t vectorize to AVX-512 for FFTs on price time series. Some problems require floating-point. Also “safety-critical” and “HFT” are different axes — HFT cares about determinism and performance, not functional safety certification standards.

u/standardese_enjoyer_42 19 points 2 hr. ago

The phrase “not mathematically defined in the domain of real number arithmetic” is new vocabulary introduced in this paper. It’s doing genuine semantic work — it’s why inf * 0 (indeterminate) fails as a constant expression while nan * 2 (propagation) passes — but it appears nowhere else in the standard and has no anchor to IEEE 60559.

The operative exclusion from the proposed [expr.const] wording is:

any operand is a signaling NaN, or the result is not mathematically defined in the domain of real number arithmetic

I understand what it means in context. I suspect CWG reviewers will too. But “domain of real number arithmetic” is informal vocabulary and a future DR will hit it and have to reverse-engineer the intent from the paper’s examples.

u/cwg_wording_watcher 14 points 2 hr. ago

The standard already uses phrases like “the mathematical value of” without exhaustive formalization. In context “domain of real number arithmetic” pretty clearly means “operations with a unique answer in ℝ” — addition, multiplication (except 0×∞), etc. Agreed a footnote or IEEE 60559 cross-reference would help. Normal CWG cleanup.

u/fp_exception_veteran 31 points 1 hr. ago

The underflow treatment deserves more attention. The paper proposes that underflow — producing a denormal or zero — is well-defined and is a valid constant expression, even though it raises FE_UNDERFLOW. This is treated the same as FE_INEXACT (rounding) for constant-evaluation purposes. The paper openly admits this is “inconsistent with the rest of the design” but justifies it on pragmatic grounds: no major compiler diagnoses underflow in constexpr today.

Honest concession, probably the right call, but it means the “constant expression ↔ no floating-point exceptions raised” model has a deliberate hole. The new well-definedness guarantee for overflow says:

if the type has an infinity representation, the result of the operation shall be +∞ or −∞

Underflow has no analogous clean statement. It’s just “this won’t disqualify the expression.” If you write code that checks fegetexcept after a constexpr init, you need to know this.

u/stm32_firmware_dev 12 points 58 min. ago

Does any of this affect embedded targets without hardware IEEE 754? I’m on Cortex-M0 with emulated FP.

u/fp_exception_veteran 8 points 45 min. ago

No behavior change for you. The well-definedness guarantees apply only to types that have an infinity representation. On a target where FLT_MAX * 2 can’t produce infinity, overflow stays UB — same as today.

u/cwg_archaeologist 38 points 2 hr. ago

For those who want the backstory:

CWG2168 (~2016): CWG has an open question about whether FP overflow is UB on IEEE 754 types. They ask SG6. SG6’s guidance: overflow to infinity is well-defined at runtime; infinity should not be usable as a constant-expression subexpression. Filed and parked.

CWG2723 (later): Adds a definition of “range of representable values” for floating-point. No SG6 consultation. The definition doesn’t carve out ±∞ — effectively reverting SG6’s guidance and making overflow UB again.

Result: two DR issues pointing in opposite directions, three compilers making different choices, and a standard that can’t answer “is FLT_MAX * 2.0f undefined behavior?” without hedging.

P3899R1 untangles it. The partial deviation from the 2016 SG6 guidance — now allowing infinity propagation in constexpr — is deliberate. The original concern doesn’t hold as strongly under the current constexpr evaluation model.

u/committee_process_realist 17 points 1 hr. ago

One thing that jumps out: this paper is routed to SG6, then EWG, then CWG. Three groups, three sets of polls, plausibly spanning 2–3 meetings. For what is essentially a contradiction between two existing DRs. GCC 15 already does the right thing. By the time this is officially normative it will have been de facto standard for years. The committee process is thorough. Whether it is appropriately fast for correctness fixes is a separate question.

u/r_cpp_janitor 1 point 1 hr. agoModerator

Reminder: paper authors occasionally read these threads. Keep it technical and keep it C++. The Rust tangent has been warned.

u/[deleted] 0 points 47 min. ago

[deleted]

u/overflow_survivor 5 points 44 min. ago

what did they say

u/actually_reads_cwg_issues 23 points 42 min. ago

something about NaN being a design mistake and C++ needing to deprecate float altogether. the usual.

u/ubsan_demo_enjoyer 56 points 32 min. ago

If you want to see the current behavioral gap: https://godbolt.org/z/K3vxYaE89

UBSAN reports FLT_MAX * 2.0f as floating-point overflow under -fsanitize=undefined. After this paper, that specific case becomes well-defined on IEEE 754 implementations and UBSAN should stop flagging it at runtime. 1.0f / 0.0f would still trigger.

#include <float.h>
int main() {
    volatile float x = FLT_MAX;
    float y = x * 2.0f;  // UBSAN: value outside representable range
    return y > 0 ? 1 : 0; // returns 1: infinity > 0
}

The fix is in the standard, not in UBSAN — toolchain updates will follow once the wording lands.

u/confused_by_abstract_reader 22 points 23 min. ago

Wait so after this paper 1.0f / 0.0f is still UB? I thought the whole point was IEEE 754 conformance. Edit: ok I read section 4.3, the authors explain the signed-infinity and negative-zero edge cases. I was wrong. Edit2: still annoyed. 1.0f / 0.0f should just be +inf on any IEEE 754 type and everyone knows it.