Source
stdlib/math/math.hpp
1
// Copyright (c) 2026 BigBrain LLC. MIT-licensed (see LICENSE).2
// Original work; see ACKNOWLEDGMENTS.md for the open-source ideas we build upon.3
#pragma once5
/**6
* @file math.hpp7
* @brief cheatah `math` — scalar math: Python's `math` module plus the8
* `abs`/`min`/`max`/`pow` built-ins. `import math` to use it.9
*10
* Every function here is **pure and allocation-free** (operates on `double` /11
* `long long` by value). Unit tests: `stdlib/tests/math_test.cpp`. The whole12
* suite runs under AddressSanitizer (the `asan` preset) and Valgrind13
* (`security/run-valgrind.sh`) on every QA-gate run.14
*15
* Doc convention (see also the other stdlib headers): each function documents16
* its runtime complexity with @complexity, its heap allocation with @alloc, and17
* the @test that covers it.18
*/19
#include <cmath>20
#include <concepts>21
#include <limits>22
#include <type_traits>24
namespace cheatah::math {26
// Concepts naming what each scalar op needs, so a misuse fails with the concept's27
// name rather than a deep template error (see constrain-all-templates policy).28
/// Numeric<T>: an arithmetic type — the int/float family `abs`/`pow` operate on.29
template <typename T>30
concept Numeric = std::is_arithmetic_v<T>;31
/// Ordered<T>: `<`-comparable, so `min`/`max` can pick the smaller/larger.32
template <typename T>33
concept Ordered = requires(const T& a, const T& b) {34
{ a < b } -> std::convertible_to<bool>;35
};37
// ---- constants ----38
inline constexpr double pi = 3.14159265358979323846; ///< π.39
inline constexpr double e = 2.71828182845904523536; ///< Euler's number e.40
inline constexpr double tau = 2.0 * pi; ///< τ = 2π.41
inline constexpr double inf = std::numeric_limits<double>::infinity(); ///< +∞.42
inline constexpr double nan = std::numeric_limits<double>::quiet_NaN(); ///< quiet NaN.44
// ---- math-related built-ins (templated) ----46
/**47
* Absolute value.48
* @param x any signed value.49
* @return |@p x|.50
* @complexity O(1) time.51
* @alloc none.52
* @warning For a signed integer type the most-negative value cannot be negated:53
* `abs` of it overflows (undefined behavior).54
* @test CheatahMath.BuiltinLikeOps55
* @crtest MathCompileRun.Abs56
* @systest StdlibE2E.Math57
*/58
template <Numeric T>59
T abs(T x) { return x < T{} ? -x : x; }61
/**62
* Smallest of two-or-more values (variadic; the overloads chain to fold extra args).63
*64
* Returns a reference bound to whichever argument compares smaller; on a tie65
* (neither `b < a`) it returns @p a. Because the result is a reference into the66
* caller's arguments, it dangles when the operands are temporaries.67
* @param a,b the values to compare (`operator<` required).68
* @return a reference to the minimum.69
* @complexity O(n) in the argument count.70
* @alloc none.71
* @test CheatahMath.BuiltinLikeOps72
* @crtest MathCompileRun.Min73
* @systest StdlibE2E.Math74
*/75
template <Ordered T>76
const T& min(const T& a, const T& b) { return (b < a) ? b : a; }77
/**78
* Smallest of three-or-more values (folds the extra args onto the two-argument overload).79
* @param a,b the first two values.80
* @param rest the remaining values (`operator<` required).81
* @return a reference to the minimum.82
* @complexity O(n) in the argument count.83
* @alloc none.84
* @test CheatahMath.BuiltinLikeOps85
* @crtest MathCompileRun.Min86
* @systest StdlibE2E.Math87
*/88
template <Ordered T, Ordered... Rest>89
const T& min(const T& a, const T& b, const Rest&... rest) { return min(min(a, b), rest...); }91
/**92
* Largest of two-or-more values (variadic; the overloads chain to fold extra args).93
*94
* Returns a reference bound to whichever argument compares larger; on a tie95
* (neither `a < b`) it returns @p a. As with min, the returned reference96
* dangles if the operands are temporaries.97
* @param a,b the values to compare (`operator<` required).98
* @return a reference to the maximum.99
* @complexity O(n) in the argument count.100
* @alloc none.101
* @test CheatahMath.BuiltinLikeOps102
* @crtest MathCompileRun.Max103
* @systest StdlibE2E.Math104
*/105
template <Ordered T>106
const T& max(const T& a, const T& b) { return (a < b) ? b : a; }107
/**108
* Largest of three-or-more values (folds the extra args onto the two-argument overload).109
* @param a,b the first two values.110
* @param rest the remaining values (`operator<` required).111
* @return a reference to the maximum.112
* @complexity O(n) in the argument count.113
* @alloc none.114
* @test CheatahMath.BuiltinLikeOps115
* @crtest MathCompileRun.Max116
* @systest StdlibE2E.Math117
*/118
template <Ordered T, Ordered... Rest>119
const T& max(const T& a, const T& b, const Rest&... rest) { return max(max(a, b), rest...); }121
/**122
* Power.123
*124
* Both operands are cast to `double` and forwarded to `std::pow`, so this125
* follows IEEE-754 semantics (e.g. `pow(0, 0)` is 1, and a negative base with a126
* non-integer exponent yields NaN); integer arguments lose exactness beyond127
* 2^53.128
* @param base the base.129
* @param exp the exponent.130
* @return @p base raised to @p exp (computed as `double`).131
* @complexity O(1) time.132
* @alloc none.133
* @test CheatahMath.BuiltinLikeOps134
* @crtest MathCompileRun.Pow135
* @systest StdlibE2E.Math136
*/137
template <Numeric Base, Numeric Exp>138
double pow(Base base, Exp exp) {139
return std::pow(static_cast<double>(base), static_cast<double>(exp));140
}142
// ---- scalar functions (compiled into the library) ----143
// All of the following are O(1) time with no heap allocation.145
/**146
* Square root.147
*148
* Returns NaN (rather than throwing) for a negative radicand; `sqrt(-0.0)` is149
* `-0.0` and `sqrt(+inf)` is `+inf`.150
* @param x radicand (NaN if @p x < 0).151
* @return √@p x.152
* @complexity O(1).153
* @alloc none.154
* @test CheatahMath.ScalarFunctions155
* @crtest MathCompileRun.Sqrt156
* @systest StdlibE2E.Math157
*/158
double sqrt(double x);159
/**160
* Cube root.161
*162
* Defined for the whole real line, including negatives (unlike sqrt): the163
* result keeps the sign of @p x, so `cbrt(-8)` is `-2`.164
* @param x any real.165
* @return ∛@p x.166
* @complexity O(1).167
* @alloc none.168
* @test CheatahMath.TranscendentalAndRounding169
* @crtest MathCompileRun.Cbrt170
* @systest StdlibE2E.Math171
*/172
double cbrt(double x);173
/**174
* Absolute value of a double.175
* @param x any real.176
* @return |@p x|.177
* @complexity O(1).178
* @alloc none.179
* @test CheatahMath.TranscendentalAndRounding180
* @crtest MathCompileRun.Fabs181
* @systest StdlibE2E.Math182
*/183
double fabs(double x);184
/**185
* Round toward −∞.186
*187
* Returns the largest integral value not greater than @p x as a `double`;188
* already-integral, NaN, and ±∞ inputs are returned unchanged, and the sign of189
* zero is preserved.190
* @param x any real.191
* @return ⌊@p x⌋.192
* @complexity O(1).193
* @alloc none.194
* @test CheatahMath.ScalarFunctions195
* @crtest MathCompileRun.Floor196
* @systest StdlibE2E.Math197
*/198
double floor(double x);199
/**200
* Round toward +∞.201
*202
* Returns the smallest integral value not less than @p x as a `double`;203
* already-integral, NaN, and ±∞ inputs are returned unchanged. For @p x in204
* (−1, 0) the result is `-0.0`.205
* @param x any real.206
* @return ⌈@p x⌉.207
* @complexity O(1).208
* @alloc none.209
* @test CheatahMath.ScalarFunctions210
* @crtest MathCompileRun.Ceil211
* @systest StdlibE2E.Math212
*/213
double ceil(double x);214
/**215
* Round toward zero.216
*217
* Discards the fractional part, rounding toward zero rather than ±∞ (so it218
* differs from floor on negatives, e.g. `trunc(-2.7)` is `-2.0`); the sign of219
* @p x, NaN, and ±∞ are preserved.220
* @param x any real.221
* @return @p x with the fraction dropped.222
* @complexity O(1).223
* @alloc none.224
* @test CheatahMath.TranscendentalAndRounding225
* @crtest MathCompileRun.Trunc226
* @systest StdlibE2E.Math227
*/228
double trunc(double x);229
/**230
* Round to nearest (half away from zero).231
*232
* Halfway cases are rounded away from zero, not to even, so `round(2.5)` is `3`233
* and `round(-2.5)` is `-3` — this differs from Python's banker's rounding;234
* NaN and ±∞ pass through unchanged.235
* @param x any real.236
* @return rounded @p x.237
* @complexity O(1).238
* @alloc none.239
* @test CheatahMath.ScalarFunctions240
* @crtest MathCompileRun.Round241
* @systest StdlibE2E.Math242
*/243
double round(double x);244
/**245
* Exponential.246
*247
* Overflows to `+inf` for large @p x and underflows to `0` for large negative248
* @p x; `exp(-inf)` is `0` and `exp(+inf)` is `+inf`.249
* @param x any real.250
* @return e^@p x.251
* @complexity O(1).252
* @alloc none.253
* @test CheatahMath.TranscendentalAndRounding254
* @crtest MathCompileRun.Exp255
* @systest StdlibE2E.Math256
*/257
double exp(double x);258
/**259
* Natural logarithm.260
*261
* Returns `-inf` for @p x == 0 and NaN (rather than throwing) for negative262
* @p x; out-of-domain input never raises an exception.263
* @param x > 0.264
* @return ln(@p x).265
* @complexity O(1).266
* @alloc none.267
* @test CheatahMath.TranscendentalAndRounding268
* @crtest MathCompileRun.Log269
* @systest StdlibE2E.Math270
*/271
double log(double x);272
/**273
* Base-2 logarithm.274
*275
* Returns `-inf` for @p x == 0 and NaN for negative @p x, matching log's276
* out-of-domain behavior.277
* @param x > 0.278
* @return log₂(@p x).279
* @complexity O(1).280
* @alloc none.281
* @test CheatahMath.ScalarFunctions282
* @crtest MathCompileRun.Log2283
* @systest StdlibE2E.Math284
*/285
double log2(double x);286
/**287
* Base-10 logarithm.288
*289
* Returns `-inf` for @p x == 0 and NaN for negative @p x, matching log's290
* out-of-domain behavior.291
* @param x > 0.292
* @return log₁₀(@p x).293
* @complexity O(1).294
* @alloc none.295
* @test CheatahMath.TranscendentalAndRounding296
* @crtest MathCompileRun.Log10297
* @systest StdlibE2E.Math298
*/299
double log10(double x);300
/**301
* Sine.302
*303
* The argument is interpreted in radians; precision degrades for very large304
* magnitudes due to argument reduction, and `sin(±inf)` is NaN.305
* @param x radians.306
* @return sin(@p x).307
* @complexity O(1).308
* @alloc none.309
* @test CheatahMath.Trigonometry310
* @crtest MathCompileRun.Sin311
* @systest StdlibE2E.Math312
*/313
double sin(double x);314
/**315
* Cosine.316
*317
* The argument is interpreted in radians; precision degrades for very large318
* magnitudes due to argument reduction, and `cos(±inf)` is NaN.319
* @param x radians.320
* @return cos(@p x).321
* @complexity O(1).322
* @alloc none.323
* @test CheatahMath.Trigonometry324
* @crtest MathCompileRun.Cos325
* @systest StdlibE2E.Math326
*/327
double cos(double x);328
/**329
* Tangent.330
*331
* The argument is in radians; near the poles (odd multiples of π/2, which are332
* not exactly representable) the result is a large finite value rather than333
* ±∞, and `tan(±inf)` is NaN.334
* @param x radians.335
* @return tan(@p x).336
* @complexity O(1).337
* @alloc none.338
* @test CheatahMath.Trigonometry339
* @crtest MathCompileRun.Tan340
* @systest StdlibE2E.Math341
*/342
double tan(double x);343
/**344
* Arcsine.345
*346
* Returns a value in [−π/2, π/2]; arguments outside [−1, 1] yield NaN rather347
* than throwing.348
* @param x in [−1, 1].349
* @return asin(@p x) in radians.350
* @complexity O(1).351
* @alloc none.352
* @test CheatahMath.Trigonometry353
* @crtest MathCompileRun.Asin354
* @systest StdlibE2E.Math355
*/356
double asin(double x);357
/**358
* Arccosine.359
*360
* Returns a value in [0, π]; arguments outside [−1, 1] yield NaN rather than361
* throwing.362
* @param x in [−1, 1].363
* @return acos(@p x) in radians.364
* @complexity O(1).365
* @alloc none.366
* @test CheatahMath.Trigonometry367
* @crtest MathCompileRun.Acos368
* @systest StdlibE2E.Math369
*/370
double acos(double x);371
/**372
* Arctangent.373
*374
* Accepts the whole real line and returns a value in (−π/2, π/2), approaching375
* ±π/2 as @p x → ±∞.376
* @param x any real.377
* @return atan(@p x) in radians.378
* @complexity O(1).379
* @alloc none.380
* @test CheatahMath.Trigonometry381
* @crtest MathCompileRun.Atan382
* @systest StdlibE2E.Math383
*/384
double atan(double x);385
/**386
* Two-argument arctangent.387
*388
* Uses the signs of both arguments to select the correct quadrant, returning a389
* value in (−π, π]; it is well-defined when @p x is zero (including the390
* `atan2(0, 0)` case, which returns 0).391
* @param y,x the coordinates.392
* @return atan2(@p y, @p x) in radians.393
* @complexity O(1).394
* @alloc none.395
* @test CheatahMath.Trigonometry396
* @crtest MathCompileRun.Atan2397
* @systest StdlibE2E.Math398
*/399
double atan2(double y, double x);400
/**401
* Hypotenuse.402
*403
* Computes the 2-norm while avoiding intermediate overflow/underflow that a404
* naive `sqrt(x*x + y*y)` would suffer; returns `+inf` if either argument is405
* infinite (even when the other is NaN).406
* @param x,y the legs.407
* @return √(@p x²+@p y²) without overflow.408
* @complexity O(1).409
* @alloc none.410
* @test CheatahMath.ScalarFunctions411
* @crtest MathCompileRun.Hypot412
* @systest StdlibE2E.Math413
*/414
double hypot(double x, double y);415
/**416
* Floating-point remainder.417
*418
* Returns `x - n*y` for the integer `n` truncated toward zero, so the result419
* takes the sign of the dividend @p x (unlike a Python-style modulo); a zero420
* divisor yields NaN rather than throwing.421
* @param x,y dividend, divisor.422
* @return @p x mod @p y.423
* @complexity O(1).424
* @alloc none.425
* @test CheatahMath.TranscendentalAndRounding426
* @crtest MathCompileRun.Fmod427
* @systest StdlibE2E.Math428
*/429
double fmod(double x, double y);430
/**431
* Copy sign.432
*433
* Takes the magnitude from @p x and the sign bit from @p y; because it copies434
* the IEEE sign bit, it distinguishes `+0.0` from `-0.0` and works even when435
* @p x is NaN.436
* @param x magnitude source,437
* @param y sign source.438
* @return |@p x| with @p y's sign.439
* @complexity O(1).440
* @alloc none.441
* @test CheatahMath.TranscendentalAndRounding442
* @crtest MathCompileRun.Copysign443
* @systest StdlibE2E.Math444
*/445
double copysign(double x, double y);446
/**447
* Radians → degrees.448
* @param radians angle in radians.449
* @return the angle in degrees.450
* @complexity O(1).451
* @alloc none.452
* @test CheatahMath.ScalarFunctions453
* @crtest MathCompileRun.Degrees454
* @systest StdlibE2E.Math455
*/456
double degrees(double radians);457
/**458
* Degrees → radians.459
* @param degrees angle in degrees.460
* @return the angle in radians.461
* @complexity O(1).462
* @alloc none.463
* @test CheatahMath.TranscendentalAndRounding464
* @crtest MathCompileRun.Radians465
* @systest StdlibE2E.Math466
*/467
double radians(double degrees);468
/**469
* Is NaN?470
*471
* The reliable NaN test, since NaN compares unequal to everything including472
* itself (so `x != x` is the only other portable check).473
* @param x any real.474
* @return true iff @p x is NaN.475
* @complexity O(1).476
* @alloc none.477
* @test CheatahMath.IsFiniteIsNanIsInf478
* @crtest MathCompileRun.Isnan479
* @systest StdlibE2E.Math480
*/481
bool isnan(double x);482
/**483
* Is infinite?484
*485
* True for both `+inf` and `-inf`; false for NaN (use isnan for that) and for486
* every finite value.487
* @param x any real.488
* @return true iff @p x is ±∞.489
* @complexity O(1).490
* @alloc none.491
* @test CheatahMath.IsFiniteIsNanIsInf492
* @crtest MathCompileRun.Isinf493
* @systest StdlibE2E.Math494
*/495
bool isinf(double x);496
/**497
* Is finite?498
* @param x any real.499
* @return true iff @p x is neither NaN nor ±∞.500
* @complexity O(1).501
* @alloc none.502
* @test CheatahMath.IsFiniteIsNanIsInf503
* @crtest MathCompileRun.Isfinite504
* @systest StdlibE2E.Math505
*/506
bool isfinite(double x);508
/**509
* Greatest common divisor.510
*511
* Operates on the absolute values via the Euclidean algorithm, so the result is512
* non-negative; `gcd(0, 0)` is 0 and `gcd(n, 0)` is `|n|`. Passing513
* `LLONG_MIN` overflows when negated.514
* @param a,b integers.515
* @return gcd(|@p a|, |@p b|).516
* @complexity O(log min(a,b)) time.517
* @alloc none.518
* @test CheatahMath.Integer519
* @crtest MathCompileRun.Gcd520
* @systest StdlibE2E.Math521
*/522
long long gcd(long long a, long long b);523
/**524
* Factorial.525
*526
* Computed by an iterative product from 2; @p n of 0 or 1 returns 1, and any527
* negative @p n also returns 1 since the loop never executes (no error is528
* raised). Results past 20! silently overflow `long long`.529
* @param n ≥ 0 (small; overflows `long long` past 20!).530
* @return @p n!.531
* @complexity O(@p n) time.532
* @alloc none.533
* @test CheatahMath.Integer534
* @crtest MathCompileRun.Factorial535
* @systest StdlibE2E.Math536
*/537
long long factorial(long long n);539
} // namespace cheatah::math