feat(math): abs/round/trunc/frexp/ldexp/scalbn/copysign/fmin/fmax/fmod

This commit is contained in:
2026-09-06 01:07:31 -04:00
parent c4c64a4eb5
commit b731a02db4
30 changed files with 5912 additions and 7 deletions
+470
View File
@@ -0,0 +1,470 @@
#ifndef VLIBC_MATH_H
#define VLIBC_MATH_H
/*
* vlibc — <math.h>.
*
* This header is ISO C core and is present in every profile. It carries the
* whole todo-39 arithmetic inventory: the classification macros
* (fpclassify/isnan/isinf/isfinite/isnormal/signbit), the constants
* (HUGE_VAL/INFINITY/NAN, FP_*, math_errhandling), and the 25 basic
* real-function families (fabs, copysign, floor, ceil, trunc, round, rint,
* nearbyint, lrint, llrint, frexp, ldexp, modf, scalbn, scalbln, fmin,
* fmax, fdim, fmod, remainder, remquo, ilogb, logb, lround, llround), each
* spelled in all three precisions (float `...f`, double, long double
* `...l`).
*
* Every declaration below lands NOW so that the header is stable across the
* todo-39 implementation slices; the slices (wip commits) fill in the
* src/math/ definitions family by family and never touch this header again.
* Later math todos extend this header in place the same way stdlib.h is
* extended: todo 40 adds the exp/log/pow families, todo 41 the trig and
* hyperbolic families, todo 42 erf/erfc/lgamma/tgamma/fma/nextafter/nan,
* and todo 43 provides <complex.h> separately.
*
* Edge-case behavior (IEEE 754-2008): floor/ceil/trunc round exact results
* with correct signed-zero and NaN/Inf handling; fabs/copysign/fmin/fmax/
* fdim never raise an exception on their own and preserve NaN payloads
* where the standard allows; the classification macros are
* exception-free. Functions that per POSIX can set errno (ldexp, scalbn,
* scalbln, fmod, remainder, remquo, and the fraction-splitters frexp/modf
* with their pointer outputs) carry no const attribute below so the
* compiler never hoists or elides an errno-setting call. Functions that
* are pure (never set errno in their defined domain — fabs, copysign,
* floor, ceil, trunc, round, rint, nearbyint, fmin, fmax, fdim, ilogb,
* logb) are declared const and fold away in static links.
*/
#include <vlibc/features.h>
#include <limits.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* ---- Constants ---- */
/*
* Positive infinity, as double/float/long double (C23 7.12.1p4). HUGE_VAL
* is also the "overflowed" return value of the strto* conversions.
*/
#define HUGE_VAL __builtin_huge_val()
#define HUGE_VALF __builtin_huge_valf()
#define HUGE_VALL __builtin_huge_vall()
/* Positive infinity as a float (C23 7.12.1p5). */
#define INFINITY __builtin_inff()
/* A quiet NaN as a float (C23 7.12.1p6); the payload is implementation
* defined but always a NaN of the quiet kind. */
#define NAN __builtin_nanf("")
/*
* Return values of fpclassify (C23 7.12.3.1): the numbers need only be
* distinct positive values; the classification macros below hand these
* exact constants to __builtin_fpclassify so the two always agree.
*/
#define FP_NAN 0
#define FP_INFINITE 1
#define FP_ZERO 2
#define FP_SUBNORMAL 3
#define FP_NORMAL 4
/*
* Return values of ilogb (C23 7.12.6.5p4): FP_ILOGB0 for a zero argument
* and FP_ILOGBNAN for a NaN or infinite argument, each the indicated
* <limits.h> sentinel.
*/
#define FP_ILOGB0 INT_MIN
#define FP_ILOGBNAN INT_MAX
/*
* Which error mechanisms the library reports through (C23 7.12.1p3):
* MATH_ERRNO and MATH_ERREXCEPT are both in effect (the domain/range
* errno values are set where POSIX requires and the corresponding
* floating-point exceptions are raised by the hardware).
*/
#define MATH_ERRNO 1
#define MATH_ERREXCEPT 2
#define math_errhandling 3
/* ---- Classification macros (type-generic, exception-free) ---- */
/*
* Classify x as NaN, infinite, zero, subnormal, or normal, returning the
* matching FP_* constant. __builtin_fpclassify is a GCC type-generic
* builtin that evaluates its floating argument exactly once and never
* traps, so no _Generic dispatch is needed here.
*/
#define fpclassify(x) \
__builtin_fpclassify(FP_NAN, FP_INFINITE, FP_NORMAL, FP_SUBNORMAL, FP_ZERO, (x))
/*
* The predicate macros below are thin wrappers over the corresponding
* GCC type-generic builtins. Each builtin evaluates its argument once and
* returns an int; integer arguments classify as finite, non-zero, and
* normal (they convert exactly), so isnan(3) is 0 and isinf(3) is 0.
*/
#define isnan(x) __builtin_isnan(x)
#define isinf(x) __builtin_isinf(x)
#define isfinite(x) __builtin_isfinite(x)
#define isnormal(x) __builtin_isnormal(x)
#define signbit(x) __builtin_signbit(x)
/* ---- Basic arithmetic families (todo 39) ---- */
/*
* Absolute value of x (C23 7.12.7.2). fabs(±0) is +0, fabs(±Inf) is +Inf,
* and fabs(NaN) is a NaN. Pure: no domain, no exception.
*/
__attribute__((const)) float
fabsf(float x);
__attribute__((const)) double
fabs(double x);
__attribute__((const)) long double
fabsl(long double x);
/*
* A value with the magnitude of x and the sign of y (C23 7.12.7.3).
* copysign(±0, y) carries y's sign; a NaN x keeps its payload but takes
* y's sign bit. Pure.
*/
__attribute__((const)) float
copysignf(float x, float y);
__attribute__((const)) double
copysign(double x, double y);
__attribute__((const)) long double
copysignl(long double x, long double y);
/*
* The largest integral value not greater than x (C23 7.12.9.2).
* floor(±0) is ±0, floor(-0.5) is -1.0, floor(±Inf) and floor(NaN)
* return their argument unchanged. Pure and exact (no inexact
* exception), so it folds.
*/
__attribute__((const)) float
floorf(float x);
__attribute__((const)) double
floor(double x);
__attribute__((const)) long double
floorl(long double x);
/*
* The smallest integral value not less than x (C23 7.12.9.1).
* ceil(±0) is ±0, ceil(-0.5) is -0.0 (a real negative zero), ceil(±Inf)
* and ceil(NaN) return their argument unchanged. Pure and exact.
*/
__attribute__((const)) float
ceilf(float x);
__attribute__((const)) double
ceil(double x);
__attribute__((const)) long double
ceill(long double x);
/*
* The integral value nearest to x in the direction of zero (C23 7.12.9.3).
* trunc(-0.7) is -0.0, trunc(0.7) is 0.0, and ±Inf/NaN pass through.
* Pure and exact.
*/
__attribute__((const)) float
truncf(float x);
__attribute__((const)) double
trunc(double x);
__attribute__((const)) long double
truncl(long double x);
/*
* The integral value nearest to x, with halfway cases rounded away from
* zero (C23 7.12.9.6). round(±0) is ±0, round(-0.5) is -1.0, and ±Inf/
* NaN pass through. Pure: round never raises the inexact exception.
*/
__attribute__((const)) float
roundf(float x);
__attribute__((const)) double
round(double x);
__attribute__((const)) long double
roundl(long double x);
/*
* The integral value nearest to x in the current rounding direction
* (C23 7.12.9.4). rint may raise the inexact exception; it is still pure
* in the const sense because it never sets errno and reads no memory.
*/
__attribute__((const)) float
rintf(float x);
__attribute__((const)) double
rint(double x);
__attribute__((const)) long double
rintl(long double x);
/*
* As rint, but guaranteed never to raise the inexact exception (C23
* 7.12.9.5). Pure and exact.
*/
__attribute__((const)) float
nearbyintf(float x);
__attribute__((const)) double
nearbyint(double x);
__attribute__((const)) long double
nearbyintl(long double x);
/*
* The nearest integral value to x in the current rounding direction,
* returned as long (C23 7.12.9.7). A result outside the range of long is
* a range error (the return value is unspecified and errno may be set),
* so no const attribute.
*/
long
lrintf(float x);
long
lrint(double x);
long
lrintl(long double x);
/*
* As lrint, returned as long long (C23 7.12.9.8). Range errors as for
* lrint; no const attribute.
*/
long long
llrintf(float x);
long long
llrint(double x);
long long
llrintl(long double x);
/*
* Split x into a fraction f in [1/2, 1) (or 0) and an integer exponent
* *exp such that x == f * 2^(*exp) (C23 7.12.6.4). Zero returns ±0 with
* *exp 0; Inf/NaN return x with an unspecified *exp. Writes *exp, so no
* const attribute.
*/
float
frexpf(float x, int *exp);
double
frexp(double x, int *exp);
long double
frexpl(long double x, int *exp);
/*
* x times 2^n (C23 7.12.6.3): the inverse of frexp. A result too large
* to represent is a range error returning ±HUGE_VAL with errno ERANGE;
* no const attribute.
*/
float
ldexpf(float x, int n);
double
ldexp(double x, int n);
long double
ldexpl(long double x, int n);
/*
* Split x into an integral part stored in *iptr and a fractional part
* returned (C23 7.12.6.5); both have x's sign, so modf(-1.5, &i) puts
* -1.0 in i and returns -0.5. Writes *iptr, so no const attribute.
*/
float
modff(float x, float *iptr);
double
modf(double x, double *iptr);
long double
modfl(long double x, long double *iptr);
/*
* x * FLT_RADIX^n with FLT_RADIX 2 (C23 7.12.6.6): the scalb* functions
* differ from ldexp only in the exponent argument's type. A result too
* large to represent is a range error returning ±HUGE_VAL* with errno
* ERANGE; no const attribute.
*/
float
scalbnf(float x, int n);
double
scalbn(double x, int n);
long double
scalbnl(long double x, int n);
float
scalblnf(float x, long n);
double
scalbln(double x, long n);
long double
scalblnl(long double x, long n);
/*
* The smaller of x and y (C23 7.12.12.4), returning -0.0 when the
* arguments are +0.0 and -0.0. A NaN argument is ignored in favor of the
* numeric one; two NaNs return a NaN. Pure.
*/
__attribute__((const)) float
fminf(float x, float y);
__attribute__((const)) double
fmin(double x, double y);
__attribute__((const)) long double
fminl(long double x, long double y);
/*
* The larger of x and y (C23 7.12.12.3), returning +0.0 when the
* arguments are +0.0 and -0.0. NaN handling as fmin. Pure.
*/
__attribute__((const)) float
fmaxf(float x, float y);
__attribute__((const)) double
fmax(double x, double y);
__attribute__((const)) long double
fmaxl(long double x, long double y);
/*
* The positive difference x - y when x > y and +0.0 otherwise (C23
* 7.12.12.2); fdim(x, NaN) and fdim(NaN, x) return a NaN. Pure in the
* domain-error-free sense (an overflow may still raise an exception
* through the hardware, which const does not model).
*/
__attribute__((const)) float
fdimf(float x, float y);
__attribute__((const)) double
fdim(double x, double y);
__attribute__((const)) long double
fdiml(long double x, long double y);
/*
* The floating-point remainder x - n*y, where n is x/y truncated toward
* zero (C23 7.12.10.1); the result therefore has x's sign. fmod(x, ±0) is
* a domain error returning NaN with errno EDOM; ±Inf/x is likewise a
* domain error. errno can be set, so no const attribute.
*/
float
fmodf(float x, float y);
double
fmod(double x, double y);
long double
fmodl(long double x, long double y);
/*
* The IEEE remainder x - n*y, where n is x/y rounded to the nearest
* integer (ties to even) (C23 7.12.10.2); |result| <= |y|/2. Domain
* errors as fmod; errno can be set, so no const attribute.
*/
float
remainderf(float x, float y);
double
remainder(double x, double y);
long double
remainderl(long double x, long double y);
/*
* As remainder, additionally storing the low bits of the integer
* quotient n in *quo (C23 7.12.10.3). Writes *quo, so no const
* attribute.
*/
float
remquof(float x, float y, int *quo);
double
remquo(double x, double y, int *quo);
long double
remquol(long double x, long double y, int *quo);
/*
* The signed exponent of x as an int (C23 7.12.6.5p1): ilogb(x) is
* floor(log2 |x|) for a nonzero finite x. ilogb(±0) returns FP_ILOGB0,
* ilogb(±Inf) and ilogb(NaN) return FP_ILOGBNAN. Pure: the sentinels are
* returned without touching errno.
*/
__attribute__((const)) int
ilogbf(float x);
__attribute__((const)) int
ilogb(double x);
__attribute__((const)) int
ilogbl(long double x);
/*
* The signed exponent of x as a floating-point value (C23 7.12.6.6):
* logb(±0) is -Inf and logb(±Inf) is +Inf, each raising the division-
* by-zero/invalid exception through the hardware but without an errno
* path in the representable domain. Pure.
*/
__attribute__((const)) float
logbf(float x);
__attribute__((const)) double
logb(double x);
__attribute__((const)) long double
logbl(long double x);
/*
* The nearest integral value to x, with halfway cases rounded away from
* zero, returned as long (C23 7.12.9.9). A result outside the range of
* long is a range error; no const attribute.
*/
long
lroundf(float x);
long
lround(double x);
long
lroundl(long double x);
/*
* As lround, returned as long long (C23 7.12.9.10). Range errors as for
* lround; no const attribute.
*/
long long
llroundf(float x);
long long
llround(double x);
long long
llroundl(long double x);
#ifdef __cplusplus
}
#endif
#endif /* VLIBC_MATH_H */
+64 -7
View File
@@ -4,15 +4,72 @@
/*
* vlibc — <tgmath.h>.
*
* Type-generic math. STUB: the type-generic dispatch over <math.h> is
* IMPLEMENTED by the first math todo (#39), not here. This stub exists so
* that including <tgmath.h> alongside the other headers compiles cleanly
* today; it deliberately defines no type-generic macros until #39 lands
* the <math.h> function inventory it dispatches to.
* Type-generic math (C23 7.25). Each macro inspects the type of its
* controlling expression and dispatches to the matching precision of the
* real <math.h> function: a float argument selects the `...f` variant, a
* long double argument the `...l` variant, and everything else (double,
* any integer type, and the _Complex types, whose imaginary part is
* dropped by the usual argument conversion) the unsuffixed double
* variant. The controlling expression is the first floating argument, so
* the two-argument forms dispatch on x and require y to be convertible to
* x's type.
*
* This header is ISO C core and is present in every profile.
* This header is ISO C core and is present in every profile. The
* classification macros (fpclassify/isnan/isinf/isfinite/isnormal/signbit)
* need no dispatch of their own — <math.h> already defines them over
* GCC's type-generic builtins, so including <math.h> below makes them
* work unchanged for every real and complex type.
*/
#include <vlibc/features.h>
#include <math.h>
/* fabs */
#define fabs(x) _Generic((x), float: fabsf, long double: fabsl, default: fabs)(x)
/* copysign */
#define copysign(x, y) \
_Generic((x), float: copysignf, long double: copysignl, default: copysign)(x, y)
/* rounding toward zero / ±Inf / current mode (floor, ceil, trunc, round,
* rint, nearbyint) */
#define floor(x) _Generic((x), float: floorf, long double: floorl, default: floor)(x)
#define ceil(x) _Generic((x), float: ceilf, long double: ceill, default: ceil)(x)
#define trunc(x) _Generic((x), float: truncf, long double: truncl, default: trunc)(x)
#define round(x) _Generic((x), float: roundf, long double: roundl, default: round)(x)
#define rint(x) _Generic((x), float: rintf, long double: rintl, default: rint)(x)
#define nearbyint(x) \
_Generic((x), float: nearbyintf, long double: nearbyintl, default: nearbyint)(x)
/* integer-returning rounding (lrint, llrint, lround, llround) */
#define lrint(x) _Generic((x), float: lrintf, long double: lrintl, default: lrint)(x)
#define llrint(x) _Generic((x), float: llrintf, long double: llrintl, default: llrint)(x)
#define lround(x) _Generic((x), float: lroundf, long double: lroundl, default: lround)(x)
#define llround(x) _Generic((x), float: llroundf, long double: llroundl, default: llround)(x)
/* fraction/exponent splitting (frexp, modf) and exponent scaling (ldexp,
* scalbn, scalbln): the second argument is an int/long or a pointer to
* the first argument's type and follows x's precision automatically */
#define frexp(x, e) _Generic((x), float: frexpf, long double: frexpl, default: frexp)(x, e)
#define modf(x, i) _Generic((x), float: modff, long double: modfl, default: modf)(x, i)
#define ldexp(x, n) _Generic((x), float: ldexpf, long double: ldexpl, default: ldexp)(x, n)
#define scalbn(x, n) _Generic((x), float: scalbnf, long double: scalbnl, default: scalbn)(x, n)
#define scalbln(x, n) _Generic((x), float: scalblnf, long double: scalblnl, default: scalbln)(x, n)
/* minimum/maximum/difference */
#define fmin(x, y) _Generic((x), float: fminf, long double: fminl, default: fmin)(x, y)
#define fmax(x, y) _Generic((x), float: fmaxf, long double: fmaxl, default: fmax)(x, y)
#define fdim(x, y) _Generic((x), float: fdimf, long double: fdiml, default: fdim)(x, y)
/* remainder (fmod, remainder, remquo: the trailing int* follows x's
* precision) */
#define fmod(x, y) _Generic((x), float: fmodf, long double: fmodl, default: fmod)(x, y)
#define remainder(x, y) \
_Generic((x), float: remainderf, long double: remainderl, default: remainder)(x, y)
#define remquo(x, y, q) \
_Generic((x), float: remquof, long double: remquol, default: remquo)(x, y, q)
/* exponent reading (ilogb returns int, logb returns x's precision) */
#define ilogb(x) _Generic((x), float: ilogbf, long double: ilogbl, default: ilogb)(x)
#define logb(x) _Generic((x), float: logbf, long double: logbl, default: logb)(x)
#endif /* VLIBC_TGMATH_H */