Module: math.basic

Source: ./math/basic.reef


Overview

math/basic module

Basic mathematical operations for integers and floating-point numbers. All floating-point functions are implemented in pure Reef using IEEE 754 bit manipulation - no libc/libm dependency.

Algorithms ported from A2 Oberon MathL.Mod (ETH Oberon System)


Functions

fn math_LN2(): float

ln(2) - natural log of 2

fn math_LN10(): float

ln(10) - natural log of 10

fn math_TWO_PI(): float

2*pi

fn math_PI(): float

pi

fn math_PI_2(): float

pi/2

fn math_PI_4(): float

pi/4

fn pow2_200(): float

Exact powers of two used by sqrt_f's exponent scaling. Built from bits so they are exact, and both are comfortably normal.

fn pow2_minus_200(): float

fn math_EPS(): float

Epsilon for convergence test (approximately 2.2e-16)

fn math_HUGE(): float

Large positive value (near max float)

fn math_TINY(): float

Small positive value for comparisons

fn float_equal(x: float, y: float): bool

fn int_max(): int

INT_MAX for a 32-bit int. INT_MIN is -(INT_MAX) - 1 and is spelled out where needed; it has no positive counterpart, which is the whole reason abs is partial.

fn abs(x: int): option.Option[int]

|x|, or None for INT_MIN.

0 - INT_MIN is INT_MIN again in two's complement, so the old if x < 0 then 0 - x returned a NEGATIVE number from a function documented to return a magnitude -- and gcd and lcm both called it.

fn checked_mul(a: int, b: int): option.Option[int]

a * b, or None when it would not fit. The guard divides rather than multiplies, so it cannot itself overflow.

fn min(a: int, b: int): int

Returns the smaller of a and b

fn max(a: int, b: int): int

Returns the larger of a and b

fn square(x: int): option.Option[int]

x * x, or None when that overflows (|x| > 46340).

fn cube(x: int): option.Option[int]

x * x * x, or None when any step overflows.

fn pow_int(base: int, exp: int): option.Option[int]

base ** exp by squaring, or None on overflow.

A negative exp is None rather than 1. The old body documented "exp must be non-negative" and then let the while e > 0 loop fall through, so pow_int(2, -1) returned 1 -- a wrong answer for an input the comment already called out as unsupported.

fn clamp(value: int, lo: int, hi: int): int

Clamps value to the range [lo, hi]

fn sign(x: int): int

Returns -1 if x < 0, 0 if x == 0, 1 if x > 0

fn gcd(a: int, b: int): option.Option[int]

Greatest common divisor, or None when the answer is |INT_MIN|.

Euclid runs on the SIGNED values and takes the magnitude only at the end. The old body called abs(a) and abs(b) up front, which handed INT_MIN to a function that could not represent its magnitude; the remainder sequence shrinks, so no intermediate here ever needs negating. None is therefore reachable only for gcd(INT_MIN, 0) and gcd(INT_MIN, INT_MIN), whose answer genuinely is 2147483648.

fn lcm(a: int, b: int): option.Option[int]

Least common multiple, or None on overflow.

reef-lang#65: this was abs(a * b) / gcd(a, b), which multiplies BEFORE dividing and so overflows on pairs whose LCM fits comfortably. Measured: lcm(196608, 65536) returned 0 -- because 196608 * 65536 is exactly 3 * 2^32, which wraps to 0 -- and 0 is also what lcm returns when an input is 0. The wrong answer was indistinguishable from a legitimate one.

Dividing first is the standard fix: a/g is exact because g divides a, and the product (a/g) * b overflows only when the true LCM is unrepresentable.

fn is_even(x: int): bool

Returns true if x is even

fn is_odd(x: int): bool

Returns true if x is odd

fn abs_f(x: float): float

Returns the absolute value of x

fn min_f(a: float, b: float): float

Returns the smaller of a and b

fn max_f(a: float, b: float): float

Returns the larger of a and b

fn trunc_f(x: float): float

Returns x truncated toward zero

fn floor_f(x: float): float

Returns the largest integer <= x (as float)

fn ceil_f(x: float): float

Returns the smallest integer >= x (as float)

fn round_f(x: float): float

Returns x rounded to the nearest integer (as float) Rounds half away from zero

fn exp_f(x: float): float

fn log_f(x: float): float

fn log10_f(x: float): float

fn next_up_pos(x: float): float

The next representable double above / below a POSITIVE finite value. For positive doubles the IEEE-754 bit pattern is monotone, so stepping the bits by one steps the value by exactly one ulp -- no magnitude constant and no special case per exponent, which matters because an ulp is not a fixed quantity. Only valid for x > 0 and finite; sqrt_correct guarantees both.

fn next_down_pos(x: float): float

fn square_err(a: float, p: float): float

Rounding error of aa, so that aa == (aa) + square_err(a, aa) EXACTLY.

Veltkamp splitting: multiplying by 2^27 + 1 and subtracting back splits a double into a high part with at most 26 significant bits and a low remainder, both exact. Products of the halves then fit a double with no rounding, so their combination recovers the bits the single rounding of a*a discarded. Needs only multiply and subtract -- no fma, no runtime help.

Safe against overflow here: sqrt_correct only calls it with c ~ sqrt(x), so c <= ~1.3e154 and 134217729 * c stays well under the double maximum.

fn sqrt_residual(x: float, c: float): float

x - cc, computed exactly enough to order candidates that differ by one ulp. cc is recovered exactly as p + err; x - p is itself exact whenever p is within a factor of two of x (Sterbenz), which holds because every candidate is within an ulp of the true root.

fn sqrt_correct(x: float, y: float): float

Pick the candidate whose square is nearest x. Comparing |cc - x| orders the candidates the same way as |c - sqrt(x)| does, because cc - x ~ 2c(c - sqrt(x)) and all three candidates share the same c to within an ulp.

fn sqrt_newton(x: float): float

Newton-Raphson to the floating-point fixed point.

reef-lang#57: this used to stop on float_equal(y, prev), an ABSOLUTE 2.2e-16. Below 1 that tolerance is wider than an ulp, so it exited while still 1-2 ulp out. The test is now exact equality, so the iteration runs until it genuinely stops moving.

fn sqrt_f(x: float): float

fn cbrt_f(x: float): float

fn is_inf_f(x: float): bool

True for +/-infinity only. Exponent 2047 with a zero mantissa is infinity; a non-zero mantissa there is NaN.

fn hypot_f(x: float, y: float): float

fn fmod_f(x: float, y: float): float

fn pow_f(base: float, exponent: float): float

fn sin_f(x: float): float

Sine function using Taylor series sin(x) = x - x^3/3! + x^5/5! - x^7/7! + ...

fn cos_f(x: float): float

Cosine function using Taylor series cos(x) = 1 - x^2/2! + x^4/4! - x^6/6! + ...

fn tan_f(x: float): float

Tangent function: tan(x) = sin(x) / cos(x)

fn atan_f(x: float): float

Arctangent: atan(x)

Reduction, in this order, so the Taylor series below is only ever evaluated where it converges fast: x < 0 : atan is odd, atan(x) = -atan(-x) x > 1 : atan(x) = pi/2 - atan(1/x) x > tan(pi/8): atan(x) = pi/4 + atan((x-1)/(x+1)) otherwise : Taylor series, |x| <= tan(pi/8)

reef-lang#56: the third step is the one that was missing, and without it this function was wrong by 5.0e-4 near |x| = 1. The series atan(x) = x - x^3/3 + x^5/5 - ... converges slower the closer |x| gets to 1, and AT x = 1 it IS the Leibniz series for pi/4, whose error after n terms is ~1/(2n). The loop below caps at 500 terms, so it exited on the cap rather than on convergence and returned ~1e-3 short. The function was not even monotonic across the seam: atan_f(1.0) returned a value below the true atan(0.999). Because x > 1 folds back into [0, 1] via the pi/2 identity, the whole neighbourhood of 1 was affected, not just the point.

It propagated structurally, which is why the fix belongs here and not in each caller: asin_f(x) = atan_f(x / sqrt(1 - x^2)) maps x = sqrt(2)/2 onto EXACTLY atan(1.0); acos_f is pi/2 - asin_f; and atan2_f(y, x) for x > 0 is atan_f(y/x), so y == x hits the same point.

Reducing at tan(pi/8) maps [tan(pi/8), 1] onto |t| <= tan(pi/8) ~ 0.4142, where successive terms shrink by t^2 ~ 0.17

The threshold literal below is the correctly rounded double for tan(pi/8). tan(pi/8) equals sqrt(2) - 1 in the reals, but do NOT substitute a computed sqrt_f(2.0) - 1.0: that lands 2 ulp away, and the threshold is compared against directly. Keep the literal. -- about 22 terms for 1e-17, comfortably inside the 500-term cap.

fn atan_series(x: float): float

Taylor series for atan, valid for any |x| <= 1 but only used by atan_f where |x| <= tan(pi/8) so that convergence is fast. Kept as its own function so the reduced-argument call above cannot accidentally re-enter the reduction and recurse.

fn atan2_f(y: float, x: float): float

Two-argument arctangent: atan2(y, x) = angle of point (x, y) Returns angle in radians in range (-pi, pi]

fn asin_f(x: float): float

Arcsine: asin(x) asin(x) = atan(x / sqrt(1 - x^2)) Domain: [-1, 1]

fn acos_f(x: float): float

Arccosine: acos(x) acos(x) = pi/2 - asin(x) Domain: [-1, 1]

fn sinh_f(x: float): float

Hyperbolic sine: sinh(x) = (e^x - e^(-x)) / 2

fn cosh_f(x: float): float

Hyperbolic cosine: cosh(x) = (e^x + e^(-x)) / 2

fn tanh_f(x: float): float

Hyperbolic tangent: tanh(x) = sinh(x) / cosh(x) = (e^x - e^(-x)) / (e^x + e^(-x))

fn log2_f(x: float): float


Generated by reefc doc