std.math
std.math provides floating-point functions with recoverable domain and
overflow errors, checked rounding to integers, integer algorithms, generic
numeric helpers, and constants. Its source is stdlib/std/math.plenty.
Importing, the reserved std namespace, and compilation are common
to every standard module.
import std.math as math
from std.math import pi, MathError
Domain algorithms report MathError.Domain and MathError.Overflow.
Integer magnitude and reduction helpers preserve OverflowError[T], including
its wrapped numeric payload. No function in this module allocates.
import std.math as math
from std.math import MathError
def main() -> Result[(), AllocError | IoError | MathError]:
print(math.sqrt(2.0)?, math.log10(1000.0)?, math.round(2.5)?)?
print(str.repr(math.sqrt(-1.0))?)?
print(math.isclose(0.1 + 0.2, 0.3, 1e-9, 0.0)?)?
print(math.gcd(84u64, 36u64), math.comb(10u64, 3u64)?)?
print(math.max(3, 9), math.abs_float(-4.5), math.pi)?
Ok(())
1.4142135623730951 3.0 2
Result[f64, std.math.MathError].Err(std.math.MathError.Domain)
True
12 120
9 4.5 3.141592653589793
Floating-point functions
Inputs are f64, following Python’s double-precision math surface. Explicitly
convert integers and f32 inputs. Angles use radians.
| Functions | Result |
|---|---|
sqrt(x), cbrt(x) | Result[f64, MathError] |
exp(x), exp2(x), expm1(x) | Result[f64, MathError] |
log(x), log2(x), log10(x), log1p(x), log_base(x, base) | Result[f64, MathError] |
sin(x), cos(x), tan(x), asin(x), acos(x), atan(x), atan2(y, x) | Result[f64, MathError] |
sinh(x), cosh(x), tanh(x), asinh(x), acosh(x), atanh(x) | Result[f64, MathError] |
pow(x, y), fmod(x, y) | Result[f64, MathError] |
floor(x), ceil(x), trunc(x), round(x) | Result[i64, MathError] |
divmod_float(a, b) | Result[(f64, f64), MathError] |
fabs(x), copysign(x, y), degrees(x), radians(x) | f64 |
isnan(x), isinf(x), isfinite(x) | bool |
isclose(a, b, rel_tol, abs_tol) | Result[bool, MathError] |
MathError.Domain reports invalid real operations, including negative square
roots, nonpositive logarithms, infinite trigonometric inputs, and zero finite
negative powers. MathError.Overflow reports finite inputs whose result
exceeds the floating or integer result range. Underflow succeeds. NaNs
propagate through floating results; rounding NaN is a domain error. Rounding
infinity or a value outside [-2**63, 2**63) overflows. Floating primitives
preserve signed zero; fabs clears its sign and copysign observes it.
Predicates and angular conversions have no recoverable failure. Angular
conversions follow IEEE arithmetic, including overflow to infinity. Integer
.pow remains a distinct exact operation with the language’s recoverable
integer arithmetic contract.
round rounds to the nearest integer, choosing the even integer at a halfway
value: round(2.5) is Ok(2) and round(3.5) is Ok(4). It takes no decimal
places argument. The numeric .round() method keeps the float type and signed
zero; the library function checks the rounded value before converting to i64.
divmod_float returns the floor quotient and a remainder with the divisor’s
sign, deriving both from the same floating remainder to avoid inconsistent
results near integral quotients. Zero remainders retain the divisor’s sign;
zero quotients retain the sign of a / b. A zero divisor or any non-finite
input is Domain; a quotient beyond the finite float range is Overflow.
Floating rounding means a == q * b + r need not hold exactly.
isclose follows Python’s weak symmetric comparison: exact equality succeeds
first, unequal infinities fail, and otherwise either relative tolerance or the
absolute tolerance may accept the difference. Negative tolerances produce
Domain; NaNs are never close. Both tolerances are required parameters;
1e-9, 0.0 matches Python’s defaults.
The module exports pi, e, tau, inf, and nan as immutable f64
module constants. They can also be imported
individually. Numeric types retain f64.PI, f64.E, f64.TAU, f64.INF, and
f64.NAN, with the same names on f32.
Integer algorithms and generic helpers
| Function | Contract |
|---|---|
factorial(n: NonNegative[i64]) -> Result[u64, MathError] | Inputs above 20 overflow |
gcd(a: u64, b: u64) -> u64 | Euclidean algorithm; gcd(0, 0) is zero |
lcm(a: u64, b: u64) -> Result[u64, MathError] | Either zero input gives zero; checked multiplication |
isqrt(n: u64) -> u64 | Exact floor square root over the entire u64 range |
perm(n: u64, k: u64) -> Result[u64, MathError] | Ordered selections; k > n gives zero |
comb(n: u64, k: u64) -> Result[u64, MathError] | Unordered selections; k > n gives zero; cancels factors before multiplication |
abs[T: IntType](x: T) -> Result[T, OverflowError[T]] | Integer magnitude; a signed minimum returns its wrapped overflow payload |
abs_float[T: FloatType](x: T) -> T | Floating magnitude; clears the sign of zero |
min[T](a: T, b: T) -> T, max[T](a: T, b: T) -> T | Compare two owned comparable values; equal values retain the first |
min_all[T](items: &list[T]) -> Option[T], max_all[T](items: &list[T]) -> Option[T] | Copy the first extremum; Nothing for an empty list; elements must be copyable and comparable |
sum[T: IntType](items: &list[T], start: T) -> Result[T, OverflowError[T]], prod[T: IntType](items: &list[T], start: T) -> Result[T, OverflowError[T]] | Sequential integer fold; first overflow returns its wrapped payload; empty lists return Ok(start) |
sum_float[T: FloatType](items: &list[T], start: T) -> T, prod_float[T: FloatType](items: &list[T], start: T) -> T | Sequential IEEE floating fold; empty lists return the explicit start |
sum_iter[I, T: IntType](items: &I, start: T) -> Result[T, OverflowError[T]], prod_iter[I, T: IntType](items: &I, start: T) -> Result[T, OverflowError[T]] | Borrowed list, set, range, or dictionary-key integer fold |
sum_iter_float[I, T: FloatType](items: &I, start: T) -> T, prod_iter_float[I, T: FloatType](items: &I, start: T) -> T | Borrowed floating fold; elements must match the start type |
divmod[T: IntType](a: T, b: T) -> Result[(T, T), MathError] | Same-type integer floor quotient and remainder; zero divisor is Domain, signed minimum divided by -1 is Overflow |
Integer combinatorics report Overflow when the result cannot fit u64.
Unlike Python, Plenty has no arbitrary-precision integer type. gcd and lcm
take two unsigned inputs; convert signed magnitudes explicitly. Generic helpers
specialize against operations in their bodies, with normal language errors for
unsupported types. They allocate nothing. Integer helpers return ordinary
public Result values even when called inside unchecked:; caller modes
do not change a callee’s contract. sum_float uses sequential IEEE 754
addition, with no compensated summation guarantee; any NaN element makes the
sum NaN, and overflow may produce infinity. The separate float names avoid
requiring impossible integer error handling from float callers.
Combinatorics and divmod handle local arithmetic results and preserve their
established MathError contract. The infallible unsigned gcd remainder and
isqrt loop use explicit unchecked: regions with bounds documented in
source. These are proven-bounded operations, not wrapping arithmetic.
Collection helpers borrow their inputs and preserve them. For f32 and f64,
min, max, min_all, and max_all follow IEEE 754-2019 minimum and
maximum: -0.0 orders below 0.0, so min(0.0, -0.0) is -0.0 and
max(-0.0, 0.0) is 0.0, and any NaN input makes the result NaN (Some(NaN)
for the list helpers), regardless of its position. The NaN returned is the
first NaN input. The pipeline min() and max()
terminals share this rule.
Other types use ordinary comparisons and retain the first of equal values.
Tuple elements retain their existing lexicographic total
order, including the ordering of NaN fields;
they are not scalar NaNs. Dictionary
folds visit keys in insertion order; set order is unspecified. The fold start is
always explicit and determines the result type. Generators and custom iterator
protocols are not borrowed iterables; strings have nonnumeric elements.
Runtime boundary and current limits
Only numeric methods and constants are compiler/runtime primitives. Platform
transcendentals, cancellation-sensitive expm1/log1p, and IEEE sign operations
need accurate scalar operations that ordinary arithmetic cannot substitute for.
The existing numeric-method boundary exposes these to f32, f64, and generic
FloatType functions, so user libraries can use them too. Algorithms, predicates,
conversions, tolerance comparisons, guards, and error construction remain Plenty
source. Generic arithmetic and integer combinatorics need no runtime additions.
This first module is inspired by CPython’s Doc/library/math.rst and
Modules/mathmodule.c, particularly its domain/overflow policy and isclose.
It is not the complete Python math API. Missing operations include hypot,
dist, fsum, sumprod, frexp, ldexp, nextafter, ulp, remainder,
fma, special functions, and newer pi-scaled trigonometry. Accurate summation
and bit-level float decomposition need separate contracts and tests; simple
arithmetic substitutes must not imply Python’s numerical guarantees.
Arbitrary constant expressions and custom iterator protocols remain language
design questions. % remains integer-only; use fmod or
divmod_float for floats. Powers use .pow() or math.pow; ** is rejected
with a diagnostic naming .pow(). Remaining scheduling belongs in the
backlog.
Acknowledgements
- API modelled on Python’s
math, including CPython’s domain and overflow policy.