Rebasing <cmath> on C23

Document number:
D3935R0
Date:
2025-11-27
Audience:
SG22
Project:
ISO/IEC 14882 Programming Languages — C++, ISO/IEC JTC1/SC22/WG21
Reply-to:
Jan Schultke <janschultke@gmail.com>
GitHub Issue:
wg21.link/P3935/github
Source:
github.com/eisenwave/cpp-proposals/blob/master/src/cmath-c23.cow

Most of C++26 is based on C23, but the header <cmath> is still based on C17. There are many useful C23 <math.h> features that should be provided in C++.

Contents

1

Introduction

2

Design

2.1

New functions

2.1.1

Narrow rounding functions

2.1.2

Canonical floating-point representations and canonicalization

2.2

New macros

3

Implementation experience

4

Wording

4.1

[basic.fundamental]

4.2

[version.syn]

4.3

[cmath.syn]

4.4

[c.math.narrow]

5

Acknowledgements

6

References

1. Introduction

[P3348R4] rebased the C++26 standard on C23; it previously referred to C17. However, this process was deliberately left incomplete: some of the new C23 <math.h> features are only used by decimal floating-point types, or they require core language changes, etc.

The goal of this proposal is to pull in all the new C23 <math.h> features which are useful not only to decimal floating-point numbers.

Introducing decimal floating-point numbers into C++ is out-of scope. That is, adding C's _Decimal128 type as a std::decimal128_t alias is not proposed. However, this proposal lays a bit of groundwork to make that happen in the future, such as introducing the concept of canonical representations into the core language.

2. Design

2.1. New functions

The functions in the table below are proposed for inclusion in C++29. The placeholder F is a shorthand for the floating-point-type placeholder in [cmath.syn]. In addition to the functions using an F placeholder, single-type functions such as float acospif(float) are proposed, but are not listed in the table for the sake of brevity; refer to §4. Wording for the complete list.

Function Returns
bool iscanonical(F x); true if and only if x is canonical
bool issignaling(F x); true if and only if x is a signaling NaN
bool issubnormal(F x); true if and only if x is subnormal
bool iszero(F x); true if and only if x is zero
F acospi(F x); arccos(x) ÷ π in the interval [ 0,1 ]
F asinpi(F x); arcsin(x) ÷ π in the interval [ 12, +12]
F atanpi(F x); arctan(x) ÷ π in the interval [ 12, +12 ]
F atan2pi(F y, F x); atan2(x,y) ÷ π in the interval [ 1, +1 ]
F cospi(F x); cos( x×π )
F sinpi(F x); sin( x×π )
F tanpi(F x); tan( x×π )
F exp10(F x); 10x
F exp10m1(F x); 10x 1
F exp2m1(F x); 2x 1
F log10p1(F x); log10 ( 1+x )
F logp1(F x); log( 1+x )
F log2p1(F x); log2 ( 1+x )
F compoundn(F x, long long n); ( 1+x ) n
F pown(F x, long long n); x n
F powr(F y, F x); e y loge x
F rootn(F x, long long n); x 1n
F rsqrt(F x); 1 x
F roundeven(F x); x rounded to the nearest integer,
rounding halfway cases to even
F fromfp(F x, int rnd, unsigned width); x rounded to a signed width-bit integer,
or NaN if x is not in range;
rnd is the rounding mode
F ufromfp(F x, int rnd, unsigned width); x rounded to an unsigned width-bit integer,
or NaN if x is not in range;
rnd is the rounding mode
F fromfpx(F x, int rnd, unsigned width); fromfp(x, rnd, width);
may also raise FE_INEXACT
F ufromfpx(F x, int rnd, unsigned width); ufromfp(x, rnd, width);
may also raise FE_INEXACT
bool canonicalize(F* cx, const F* x); true if and only if canonicalization failed
F fmaximum_mag(F x, F y); the value with greater magnitude,
or fmaximum(x, y)
F fminimum_mag(F x, F y); the value with greater magnitude,
or fminimum(x, y)
F fmaximum_mag_num(F x, F y); the value with greater magnitude,
or fmaximum_num(x, y)
F fminimum_mag_num(F x, F y); the value with greater magnitude,
or fminimum_num(x, y)
float fadd(F x, F y);
double dadd(F x, F y);
x+y
float fsub(F x, F y);
double dsub(F x, F y);
xy
float fmul(F x, F y);
double dmul(F x, F y);
x×y
float fdiv(F x, F y);
double ddiv(F x, F y);
x÷y
float ffma(F x, F y, F z);
double dfma(F x, F y, F z);
(x×y) +z
float fsqrt(F x);
double dsqrt(F x);
x
bool iseqsig(F x, F y) true if x and y are equal and
false otherwise

2.1.1. Narrow rounding functions

The narrow rounding functions (such as fadd) are unusually difficult to expose in C++ in a generic way. Even the <tgmath.h> versions require the user to specify the return type as a prefix (f, d, f32, …) as part of the function name. This is extremely hostile to generic code, and also means there is no support for extended floating-types in C++ because we don't consider std::float32_t to be C23's _Float32 and to have sqrtf32 and other functions that accept it.

To solve this, we can do something novel: add function templates for these rounding functions:

template<class T, class F1, class F2> constexpr T fadd(F1 x, F2 y) noexcept; template<class T, class F1, class F2> constexpr T fsub(F1 x, F2 y) noexcept; template<class T, class F1, class F2> constexpr T fmul(F1 x, F2 y) noexcept; template<class T, class F1, class F2> constexpr T fdiv(F1 x, F2 y) noexcept; template<class T, class F1, class F2, class F3> constexpr T ffma(F1 x, F2 y, F3 z) noexcept; template<class T, class F1> constexpr T fsqrt(F1 x) noexcept;

While the prefix f is a bit inappropriate (it it supposed to indicate a float return type), there is no obvious alternative prefix, f definitely won't clash with future <math.h> functions, and having no prefix at all would create undesirable overload sets with other functions.

The design goal for these functions is for the user to be able to write:

#ifdef __cplusplus #define f32add(...) ::std::fadd<std::float32_t>(__VA_ARGS__) // ... #else #include <tgmath.h> #endif

There is also a non-template std::fadd function which is meant to emulate the behavior of the <tgmath.h> macro by selecting between the long doublefloat and doublefloat overloads.

2.1.2. Canonical floating-point representations and canonicalization

C23 introduced the concept of "canonical representations" identified by iscanonical, and a function canonicalize to convert to canonical representations of the same value.

Examples of canonical/non-canonical representations include:

Unless working with decimal floating-point types, the classification of representations as "canonical" is largely based on the subjective preferences of the implementation; there are no hard rules other than a canonical representation being unique.

2.2. New macros

Besides the new functions, there are also macros which are pulled in from C23.

The int rnd parameter in functions such as fromfp corresponds to one of FP_INT_* rounding direction macros.

The block below lists all new macros:

// rounding direction macros FP_INT_UPWARD FP_INT_DOWNWARD FP_INT_TOWARDZERO FP_INT_TONEARESTFROMZERO FP_INT_TONEAREST // indicates that the respective function is "fast" FP_FAST_FADD FP_FAST_FADDL FP_FAST_DADDL FP_FAST_FSUB FP_FAST_FSUBL FP_FAST_DSUBL FP_FAST_FMUL FP_FAST_FMULL FP_FAST_DMULL FP_FAST_FDIV FP_FAST_FDIVL FP_FAST_DDIVL FP_FAST_FSQRT FP_FAST_FSQRTL FP_FAST_DSQRTL FP_FAST_FFMA FP_FAST_FFMAL FP_FAST_DFMAL

3. Implementation experience

All non-template additions are taken from C23, and most have been implemented in gnulibc.

The new narrow rounding function templates simply dispatch to some non-template overload based on the template argument T.

4. Wording

The changes are relative to [N5014].

[basic.fundamental]

Immediately prior to [basic.fundamental] paragraph 13, insert a new paragraph:

An implementation may prefer particular representations of values that have multiple representations in a floating-point type. The preferred representations of a floating-point type, including unique representations of values in the type, are called canonical. A floating-point type may also contain non-canonical representations, for example, redundant representations of some or all its values, or representations that are extraneous to the floating-point model. Typically, floating-point operations deliver results with canonical representations.

[Note: The functions std::iscanonical and std::canonicalize distinguish canonical (preferred) representations, but this distinction alone does not imply that canonical and non-canonical representations are of different values. — end note]

The C23 wording is extremely vague, and we can't be precise either if we don't want to risk having diverging behavior for iscanonical.

iscanonical and canonicalize are primarily motivated by ISO/IEC 60559 decimal floating-point representations anyway, where it is precisely specified which representations are canonical.

[version.syn]

Add a feature-test macro to [version.syn] as follows:

#define __cpp_lib_cmath 20XXXXL // also in <cmath>, <cstdlib>

Although the proposal also adds a __STDC_VERSION_MATH_H__ macro from C23, this cannot be reliably used to detect the new functions; it could have also been leaked through a <math.h> header from somewhere, without e.g. std::roundeven actually existing.

[cmath.syn]

#define __STDC_VERSION_MATH_H__ 202311L #define HUGE_VAL see below #define HUGE_VALF see below #define HUGE_VALL see below #define INFINITY see below #define NAN see below #define FP_INFINITE see below #define FP_NAN see below #define FP_NORMAL see below #define FP_SUBNORMAL see below #define FP_ZERO see below #define FP_INT_UPWARD see below #define FP_INT_DOWNWARD see below #define FP_INT_TOWARDZERO see below #define FP_INT_TONEARESTFROMZERO see below #define FP_INT_TONEAREST see below #define FP_FAST_FMA see below #define FP_FAST_FMAF see below #define FP_FAST_FMAL see below #define FP_FAST_FADD see below #define FP_FAST_FADDL see below #define FP_FAST_DADDL see below #define FP_FAST_FSUB see below #define FP_FAST_FSUBL see below #define FP_FAST_DSUBL see below #define FP_FAST_FMUL see below #define FP_FAST_FMULL see below #define FP_FAST_DMULL see below #define FP_FAST_FDIV see below #define FP_FAST_FDIVL see below #define FP_FAST_DDIVL see below #define FP_FAST_FSQRT see below #define FP_FAST_FSQRTL see below #define FP_FAST_DSQRTL see below #define FP_FAST_FFMA see below #define FP_FAST_FFMAL see below #define FP_FAST_DFMAL see below #define FP_ILOGB0 see below #define FP_ILOGBNAN see below #define MATH_ERRNO see below #define MATH_ERREXCEPT see below #define math_errhandling see below namespace std { using float_t = see below; using double_t = see below; constexpr floating-point-type acos(floating-point-type x); constexpr float acosf(float x); constexpr long double acosl(long double x); constexpr floating-point-type asin(floating-point-type x); constexpr float asinf(float x); constexpr long double asinl(long double x); constexpr floating-point-type atan(floating-point-type x); constexpr float atanf(float x); constexpr long double atanl(long double x); constexpr floating-point-type atan2(floating-point-type y, floating-point-type x); constexpr float atan2f(float y, float x); constexpr long double atan2l(long double y, long double x); constexpr floating-point-type cos(floating-point-type x); constexpr float cosf(float x); constexpr long double cosl(long double x); constexpr floating-point-type sin(floating-point-type x); constexpr float sinf(float x); constexpr long double sinl(long double x); constexpr floating-point-type tan(floating-point-type x); constexpr float tanf(float x); constexpr long double tanl(long double x); constexpr floating-point-type acospi(floating-point-type x); constexpr float acospif(float x); constexpr long double acospil(long double x); constexpr floating-point-type asinpi(floating-point-type x); constexpr float asinpif(float x); constexpr long double asinpil(long double x); constexpr floating-point-type atanpi(floating-point-type x); constexpr float atanpif(float x); constexpr long double atanpil(long double x); constexpr floating-point-type atan2(floating-point-type y, floating-point-type x); constexpr float atan2f(float y, float x); constexpr long double atan2l(long double y, long double x); constexpr floating-point-type cospi(floating-point-type x); constexpr float cospif(float x); constexpr long double cospil(long double x); constexpr floating-point-type sinpi(floating-point-type x); constexpr float sinpif(float x); constexpr long double sinpil(long double x); constexpr floating-point-type tanpi(floating-point-type x); constexpr float tanpif(float x); constexpr long double tanpil(long double x); constexpr floating-point-type acosh(floating-point-type x); constexpr float acoshf(float x); constexpr long double acoshl(long double x); constexpr floating-point-type asinh(floating-point-type x); constexpr float asinhf(float x); constexpr long double asinhl(long double x); constexpr floating-point-type atanh(floating-point-type x); constexpr float atanhf(float x); constexpr long double atanhl(long double x); constexpr floating-point-type cosh(floating-point-type x); constexpr float coshf(float x); constexpr long double coshl(long double x); constexpr floating-point-type sinh(floating-point-type x); constexpr float sinhf(float x); constexpr long double sinhl(long double x); constexpr floating-point-type tanh(floating-point-type x); constexpr float tanhf(float x); constexpr long double tanhl(long double x); constexpr floating-point-type exp(floating-point-type x); constexpr float expf(float x); constexpr long double expl(long double x); constexpr floating-point-type exp10(floating-point-type x); constexpr float exp10f(float x); constexpr long double exp10l(long double x); constexpr floating-point-type exp10m1(floating-point-type x); constexpr float exp10m1f(float x); constexpr long double exp10m1l(long double x); constexpr floating-point-type exp2(floating-point-type x); constexpr float exp2f(float x); constexpr long double exp2l(long double x); constexpr floating-point-type exp2m1(floating-point-type x); constexpr float exp2m1f(float x); constexpr long double exp2m1l(long double x); constexpr floating-point-type expm1(floating-point-type x); constexpr float expm1f(float x); constexpr long double expm1l(long double x); constexpr floating-point-type frexp(floating-point-type value, int* exp); constexpr float frexpf(float value, int* exp); constexpr long double frexpl(long double value, int* exp); constexpr int ilogb(floating-point-type x); constexpr int ilogbf(float x); constexpr int ilogbl(long double x); constexpr floating-point-type ldexp(floating-point-type x, int exp); constexpr float ldexpf(float x, int exp); constexpr long double ldexpl(long double x, int exp); constexpr floating-point-type log(floating-point-type x); constexpr float logf(float x); constexpr long double logl(long double x); constexpr floating-point-type log10(floating-point-type x); constexpr float log10f(float x); constexpr long double log10l(long double x); constexpr floating-point-type log10p1(floating-point-type x); constexpr float log10p1f(float x); constexpr long double log10p1l(long double x); constexpr floating-point-type log1p(floating-point-type x); constexpr float log1pf(float x); constexpr long double log1pl(long double x); constexpr floating-point-type logp1(floating-point-type x); constexpr float logp1f(float x); constexpr long double logp1l(long double x); constexpr floating-point-type log2(floating-point-type x); constexpr float log2f(float x); constexpr long double log2l(long double x); constexpr floating-point-type log2p1(floating-point-type x); constexpr float log2p1f(float x); constexpr long double log2p1l(long double x); constexpr floating-point-type logb(floating-point-type x); constexpr float logbf(float x); constexpr long double logbl(long double x); constexpr floating-point-type modf(floating-point-type value, floating-point-type* iptr); constexpr float modff(float value, float* iptr); constexpr long double modfl(long double value, long double* iptr); constexpr floating-point-type scalbn(floating-point-type x, int n); constexpr float scalbnf(float x, int n); constexpr long double scalbnl(long double x, int n); constexpr floating-point-type scalbln(floating-point-type x, long int n); constexpr float scalblnf(float x, long int n); constexpr long double scalblnl(long double x, long int n); constexpr floating-point-type cbrt(floating-point-type x); constexpr float cbrtf(float x); constexpr long double cbrtl(long double x); constexpr floating-point-type compoundn(floating-point-type x, long long int n); constexpr float compoundn(float x, long long int n); constexpr long double compoundn(long double x, long long int n); // [c.math.abs], absolute values constexpr int abs(int j); // freestanding constexpr long int abs(long int j); // freestanding constexpr long long int abs(long long int j); // freestanding constexpr floating-point-type abs(floating-point-type j); // freestanding-deleted constexpr floating-point-type fabs(floating-point-type x); constexpr float fabsf(float x); constexpr long double fabsl(long double x); constexpr floating-point-type hypot(floating-point-type x, floating-point-type y); constexpr float hypotf(float x, float y); constexpr long double hypotl(long double x, long double y); // [c.math.hypot3], three-dimensional hypotenuse constexpr floating-point-type hypot(floating-point-type x, floating-point-type y, floating-point-type z); constexpr floating-point-type pow(floating-point-type x, floating-point-type y); constexpr float powf(float x, float y); constexpr long double powl(long double x, long double y); constexpr floating-point-type pown(floating-point-type x, long long int n); constexpr float pownf(float x, long long int n); constexpr long double pownl(long double x, long long long int n); constexpr floating-point-type powr(floating-point-type x, floating-point-type y); constexpr float powrf(float x, float y); constexpr long double powrl(long double x, long double y); constexpr floating-point-type rootn(floating-point-type x); constexpr float rootnf(float x); constexpr long double rootnl(long double x); constexpr floating-point-type rsqrt(floating-point-type x); constexpr float rsqrtf(float x); constexpr long double rsqrtl(long double x); constexpr floating-point-type sqrt(floating-point-type x); constexpr float sqrtf(float x); constexpr long double sqrtl(long double x); constexpr floating-point-type erf(floating-point-type x); constexpr float erff(float x); constexpr long double erfl(long double x); constexpr floating-point-type erfc(floating-point-type x); constexpr float erfcf(float x); constexpr long double erfcl(long double x); constexpr floating-point-type lgamma(floating-point-type x); constexpr float lgammaf(float x); constexpr long double lgammal(long double x); constexpr floating-point-type tgamma(floating-point-type x); constexpr float tgammaf(float x); constexpr long double tgammal(long double x); constexpr floating-point-type ceil(floating-point-type x); constexpr float ceilf(float x); constexpr long double ceill(long double x); constexpr floating-point-type floor(floating-point-type x); constexpr float floorf(float x); constexpr long double floorl(long double x); floating-point-type nearbyint(floating-point-type x); float nearbyintf(float x); long double nearbyintl(long double x); floating-point-type rint(floating-point-type x); float rintf(float x); long double rintl(long double x); long int lrint(floating-point-type x); long int lrintf(float x); long int lrintl(long double x); long long int llrint(floating-point-type x); long long int llrintf(float x); long long int llrintl(long double x); constexpr floating-point-type round(floating-point-type x); constexpr float roundf(float x); constexpr long double roundl(long double x); constexpr long int lround(floating-point-type x); constexpr long int lroundf(float x); constexpr long int lroundl(long double x); constexpr long long int llround(floating-point-type x); constexpr long long int llroundf(float x); constexpr long long int llroundl(long double x); constexpr floating-point-type roundeven(floating-point-type x); constexpr float roundevenf(float x); constexpr long double roundevenl(long double x); constexpr floating-point-type trunc(floating-point-type x); constexpr float truncf(float x); constexpr long double truncl(long double x); constexpr floating-point-type fromfp(floating-point-type x, int rnd, unsigned int width); constexpr float fromfpf(float x, int rnd, unsigned int width); constexpr long double fromfpl(long double x, int rnd, unsigned int width); constexpr floating-point-type ufromfp(floating-point-type x, int rnd, unsigned int width); constexpr float ufromfpf(float x, int rnd, unsigned int width); constexpr long double ufromfpl(long double x, int rnd, unsigned int width); constexpr floating-point-type fromfpx(floating-point-type x, int rnd, unsigned int width); constexpr float fromfpxf(float x, int rnd, unsigned int width); constexpr long double fromfpxl(long double x, int rnd, unsigned int width); constexpr floating-point-type ufromfpx(floating-point-type x, int rnd, unsigned int width); constexpr float ufromfpxf(float x, int rnd, unsigned int width); constexpr long double ufromfpxl(long double x, int rnd, unsigned int width); constexpr floating-point-type fmod(floating-point-type x, floating-point-type y); constexpr float fmodf(float x, float y); constexpr long double fmodl(long double x, long double y); constexpr floating-point-type remainder(floating-point-type x, floating-point-type y); constexpr float remainderf(float x, float y); constexpr long double remainderl(long double x, long double y); constexpr floating-point-type remquo(floating-point-type x, floating-point-type y, int* quo); constexpr float remquof(float x, float y, int* quo); constexpr long double remquol(long double x, long double y, int* quo); constexpr floating-point-type copysign(floating-point-type x, floating-point-type y); constexpr float copysignf(float x, float y); constexpr long double copysignl(long double x, long double y); double nan(const char* tagp); float nanf(const char* tagp); long double nanl(const char* tagp); constexpr floating-point-type nextafter(floating-point-type x, floating-point-type y); constexpr float nextafterf(float x, float y); constexpr long double nextafterl(long double x, long double y); constexpr floating-point-type nexttoward(floating-point-type x, long double y); constexpr float nexttowardf(float x, long double y); constexpr long double nexttowardl(long double x, long double y); constexpr floating-point-type nextup(floating-point-type x); constexpr float nextupf(float x); constexpr long double nextupl(long double x); constexpr floating-point-type nextdown(floating-point-type x); constexpr float nextdownf(float x); constexpr long double nextdownl(long double x); constexpr bool canonicalize(floating-point-type* cx, const floating-point-type* x); constexpr bool canonicalizef(float cx, const float* x); constexpr bool canonicalizel(long double cx, const long double* x); constexpr floating-point-type fdim(floating-point-type x, floating-point-type y); constexpr float fdimf(float x, float y); constexpr long double fdiml(long double x, long double y); constexpr floating-point-type fmax(floating-point-type x, floating-point-type y); constexpr float fmaxf(float x, float y); constexpr long double fmaxl(long double x, long double y); constexpr floating-point-type fmin(floating-point-type x, floating-point-type y); constexpr float fminf(float x, float y); constexpr long double fminl(long double x, long double y); constexpr floating-point-type fmaximum(floating-point-type x, floating-point-type y); constexpr floating-point-type fmaximum_num(floating-point-type x, floating-point-type y); constexpr floating-point-type fmaximum_mag(floating-point-type x, floating-point-type y); constexpr floating-point-type fmaximum_mag_num(floating-point-type x, floating-point-type y); constexpr floating-point-type fminimum(floating-point-type x, floating-point-type y); constexpr floating-point-type fminimum_num(floating-point-type x, floating-point-type y); constexpr floating-point-type fminimum_mag(floating-point-type x, floating-point-type y); constexpr floating-point-type fminimum_mag_num(floating-point-type x, floating-point-type y); constexpr floating-point-type fma(floating-point-type x, floating-point-type y, floating-point-type z); constexpr float fmaf(float x, float y, float z); constexpr long double fmal(long double x, long double y, long double z); // [c.math.narrow], narrowing operations template<class T, class F> constexpr T fadd(F x, F y) noexcept; constexpr float fadd(double-type x, double-type y); constexpr float faddl(long double x, long double y); constexpr double dadd(long double x, long double y); constexpr double daddl(long double x, long double y); template<class T, class F> constexpr T fsub(F x, F y) noexcept; constexpr float fsub(double-type x, double-type y); constexpr float fsubl(long double x, long double y); constexpr double dsub(long double x, long double y); constexpr double dsubl(long double x, long double y); template<class T, class F> constexpr T fmul(F x, F y) noexcept; constexpr float fmul(double-type x, double-type y); constexpr float fmull(long double x, long double y); constexpr double dmul(long double x, long double y); constexpr double dmull(long double x, long double y); template<class T, class F> constexpr T fdiv(F x, F y) noexcept; constexpr float fdiv(double-type x, double-type y); constexpr float fdivl(long double x, long double y); constexpr double ddiv(long double x, long double y); constexpr double ddivl(long double x, long double y); template<class T, class F> constexpr T ffma(F x, F y, F z) noexcept; constexpr float ffma(double-type x, double-type y, double-type z); constexpr float ffmal(long double x, long double y, long double z); constexpr double dfma(long double x, long double y, long double z); constexpr double dfmal(long double x, long double y, long double z); template<class T, class F> constexpr T fsqrt(F x) noexcept; constexpr float fsqrt(double-type x); constexpr float fsqrtl(long double x); constexpr double dsqrt(long double x); constexpr double dsqrtl(long double x); // [c.math.lerp], linear interpolation constexpr floating-point-type lerp(floating-point-type a, floating-point-type b, floating-point-type t) noexcept; // [c.math.fpclass], classification / comparison functions constexpr int fpclassify(floating-point-type x); constexpr bool iscanonical(floating-point-type x); constexpr bool isfinite(floating-point-type x); constexpr bool isinf(floating-point-type x); constexpr bool isnan(floating-point-type x); constexpr bool isnormal(floating-point-type x); constexpr bool signbit(floating-point-type x); constexpr bool issignaling(floating-point-type x); constexpr bool issubnormal(floating-point-type x); constexpr bool iszero(floating-point-type x); constexpr bool isgreater(floating-point-type x, floating-point-type y); constexpr bool isgreaterequal(floating-point-type x, floating-point-type y); constexpr bool isless(floating-point-type x, floating-point-type y); constexpr bool islessequal(floating-point-type x, floating-point-type y); constexpr bool islessgreater(floating-point-type x, floating-point-type y); constexpr bool isunordered(floating-point-type x, floating-point-type y); constexpr bool iseqsig(floating-point-type x, floating-point-type y); // [sf.cmath], mathematical special functions […] }

Change [cmath.syn] paragraph 1 as follows:

The contents and meaning of the header <cmath> are a subset of the C standard library header <math.h> and only the declarations shown in the synopsis above are present, with the addition of a three-dimensional hypotenuse function, a linear interpolation function, and the mathematical special functions described in [sf.cmath].

Do not change [cmath.syn] paragraph 2:

For each function with at least one parameter of type floating-point-type, the implementation provides an overload for each cv-unqualified floating-point type ([basic.fundamental]) where all uses of floating-point-type in the function signature are replaced with that floating-point type.

Insert a new paragraph immediately following [cmath.syn] paragraph 2:

For each function with at least one parameter of type double-type, the implementation provides an overload for double and long double where all uses of double-type in the function signature are replaced with that floating-point type.

Insert a new items following that:

¶ Let common-floating-point-type and common-double-type be the following exposition-only alias templates:

template<class... Ts> using common-floating-point-type = see below; template<class... Ts> using common-double-type = see below;

Constraints: Each type in the pack Ts is a cv-unqualified arithmetic type ([basic.fundamental]).

¶ Let R be defined as follows:

  • For common-floating-point-type, let R be the floating-point type with the greatest floating-point conversion rank and greatest floating-point conversion subrank among the types in Ts, where integer types are considered to have the same floating-point conversion rank as double.
  • For common-double-type, let R be long double if any of the types in Ts is long double, and double otherwise.

Mandates: R is defined.

Result: R.

common-floating-point-type is just following the existing semantics of [cmath.syn] paragraph 3 in the form of an exposition-only alias template.

common-double-type is meant to emulate the behavior of the <tgmath.h> macros fadd, fsub, etc.

For dadd, dsub, etc. no such thing is necessary because without decimal floating-point types, the behavior is always to convert all arguments to long double.

Change [cmath.syn] paragraph 3 as follows:

For each function with at least one parameter of type floating-point-type or double-type other than abs, the implementation also provides additional overloads sufficient to ensure that, if every argument corresponding to a floating-point-type or double-type parameter has arithmetic type, then every such argument is effectively cast to the floating-point type with the greatest floating-point conversion rank and greatest floating-point conversion subrank among the types of all such arguments, where arguments of integer type are considered to have the same floating-point conversion rank as double common-floating-point-type<Args...> or common-double-type<Args...>, respectively. If no such floating-point type with the greatest rank and subrank exists that specialization of the alias template is ill-formed, then overload resolution does not result in a usable candidate ([over.match.general]) from the overloads provided by the implementation.

Add a new paragraph immediately following [cmath.syn] paragraph 3:

The implementation may provide additional overloads of padd, psub, pmul, pdiv, pfma, and psqrt, for any prefix p, with parameters of cv-unqualified arithmetic types to ensure consistent behavior with the corresponding macro in C header <tgmath.h>.

While the implementation is generally free to provide additional overloads, this is not allowed to alter the behavior of a conforming program. If an implementation provided an extended floating-point type _Decimal128, dadd would need to convert arguments of type _Decimal128 to long double, but the C23 behavior is to call daddd128.

This would not be the case if _Decimal128 was a compiler extension, in which case the program isn't standard C++ anyway, and the implementation can do whatever it wants.

[c.math.narrow]

Add a new subclause as follows:

Narrowing operations [c.math.narrow]

constexpr float fop(long double x, long double y); constexpr double dop(long double x, long double y);

¶ Let op be a placeholder for add, sub, mul, div, fma, or sqrt.

Effects: Each of the functions fop and dop has the same behavior as the functions fopl and dopl, respectively.

template<class T, class F1, class F2> constexpr T fadd(F1 x, F2 y) noexcept; template<class T, class F1, class F2> constexpr T fsub(F1 x, F2 y) noexcept; template<class T, class F1, class F2> constexpr T fmul(F1 x, F2 y) noexcept; template<class T, class F1> constexpr T fdiv(F1 x, F2 y) noexcept; template<class T, class F1, class F2, class F3> constexpr T ffma(F1 x, F2 y, F3 z) noexcept; template<class T, class F1> constexpr T fsqrt(F1 x) noexcept;

¶ Let Fs be a pack of template type parameters following T. Let F be the type determined as follows:

  • If T is double or if any type in Fs is long double, F is long double.
  • Otherwise, if T is float, F is double.
  • Otherwise, if T is an extended floating-point type named by an alias std::floatN_t ([stdfloat.syn]), F is the type named by std::floatM_t, where M=N×2, if such an alias is defined.
  • Otherwise, F is implementation-defined and may not exist.

Constraints:

  • Each type in Fs is a cv-unqualified arithmetic type.
  • F exists.
  • T is a cv-unqualified floating-point type other than long double.

[Example: It is implementation-defined whether the constraints of fadd<bfloat16_t, float32_t> are satisfied. — end example]

Effects: All parameters are converted to prvalues of type F. Each of the function templates has the same behavior as the corresponding non-template overload with the same name, if that overload had return type T and parameters all of type F.

Remarks: The FP_FAST macros also relate to specializations of these function templates.
[Example: FP_FAST_DSQRTL relates to dsqrtl and fsqrt<double, long double>. — end example]

5. Acknowledgements

Thanks to Hubert Tong for reviewing the library wording and pointing out asymmetries with the behavior of <tgmath.h> macros.

6. References

[N5014] Thomas Köppe. Working Draft, Programming Languages — C++ 2025-08-05 https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/n5014.pdf
[P3348R4] Jonathan Wakely. C++26 should refer to C23 not C17 2025-06-19 https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3348r4.pdf