Document: P3938R1
Author: Jan Schultke
Date: 2026-02-20
Audience: SG6, EWG, CWG
Jan Schultke takes on a surprisingly fundamental question: what values can a C++ floating-point type actually represent? Turns out the standard is vague on whether infinity and NaN exist from a core language perspective, whether negative zero is negative, and whether 0.0 is guaranteed to be positive zero.
The paper walks through about fifteen of these questions, finds answers in existing practice and scattered wording, and proposes core language changes to nail it all down. Notable finding: numeric_limits<float>::infinity() + 0 is technically UB under the current standard, because C++ never actually maps its expressions to ISO/IEC 60559 operations. Also clarifies template argument equivalence for floats - two NaN bit patterns with different payloads produce distinct template instantiations, and the paper proposes wording to match that.
Targets SG6, EWG, and CWG. Mostly encoding status quo into normative wording rather than proposing new behavior.
TIL that adding zero to infinity is technically undefined behavior in C++. I have been living a lie for fifteen years.
Wait until you find out that dividing by zero is also UB. Yes, even for floats. The standard says "the behavior is undefined" in [expr.mul] with zero caveats for floating-point types. Every
1.0f / 0.0fyou ever wrote was a prayer, not a program.We are 40+ years into C++ and we still haven't formally specified what values a float can hold. Just incredible. This is the kind of paper that makes you realize the standard is held together with vibes and implementation consensus.
Rust just made floats not
Eqand notOrdand called it a day. Sometimes the answer is "this is hard, so let's make the type system remind you it's hard." Meanwhile C++ is still trying to retroactively figure out if negative zero is negative.I read the whole thing. It's well-structured but the framing is slightly misleading. The abstract says:
But several of the proposed wording changes are actually normative, not just descriptive:
1. Mandating that floating-point literals have no negative sign bit ([lex.fcon]). No implementation does this differently, but it was never required before.
2. Requiring unary
-to flip the sign bit of zero ([expr.unary.op]). Again, all compilers do this, but the standard didn't say they had to.3. Defining "bitwise identical" and using it for template argument equivalence ([temp.type]). This replaces the vague "identical" with something that has a concrete operational definition via
std::bit_cast.None of these are controversial - they're all encoding what compilers already do. But "investigating what the status quo is and turning that into wording" undersells it. These are normative changes that narrow the implementation space. That's the right thing to do here. I just wish the paper owned it more directly instead of framing itself as purely descriptive.
The Q&A section (section 3) is worth reading on its own as a reference. I've had the "is negative zero negative" argument at work more than once and the answer ("no, because negative means less than zero, and
-0.0compares equal to0.0") is one of those things that's obvious once someone writes it down.Section 3.14 on template argument equivalence is the part I care about most. The current wording says floating-point template arguments must be "identical" but never defines what that means for floats. All three compilers just mangle the bit pattern into the symbol name:
Two distinct instantiations for what ISO/IEC 60559 considers the same value. The paper proposes "bitwise identical" via
std::bit_castwhich matches what compilers already do and what P1714R1 originally intended before it got rejected in favor of P1907R1.This also means
std::constant_wrapperwould work correctly with distinct NaN payloads, which is a nice side effect.NaN payloads as template arguments. That is deeply cursed and I love it.
The sNaN relaxation for
is_iec559is the right call. On WASM targets,f32/f64instructions don't handle signaling NaNs at all - they silently get treated as quiet NaNs. But GCC and Clang still reportis_iec559 == truebecause the format conforms to IEEE 754, just not the signaling behavior.This fixes a real contradiction that has bitten people writing portable numeric code. The alternative would be forcing
is_iec559 = falseon WASM, which would break a lot ofif constexprbranches that people use to select IEEE-aware code paths. Worse for everyone.