plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
plum-std/Number.plum
module std
import std/Str
import std/Bool
import std/Result
MIN_VALUE = -0x8000_0000_0000_0000 # Lowest value of Int
MAX_VALUE = 0x7FFF_FFFF_FFFF_FFFF # Highest value of Int
LARGE = 1 << 28 # 2**28
E = 2.718281828459045f # Euler's number, the base of natural logarithms, e, https://oeis.org/A001113
LN10 = 2.302585092994046f # The natural logarithm of 10, https://oeis.org/A002392
LN2 = 0.6931471805599453f # The natural logarithm of 2, https://oeis.org/A002162
LOG10E = 0.4342944819032518f # The base 10 logarithm of e, formula: 1 / LN10
LOG2E = 1.4426950408889634f # The base 2 logarithm of e, formula: 1 / LN2
PI = 3.141592653589793f # The ratio of the circumference of a circle to its diameter, https://oeis.org/A000796
PHI = 1.618033988749895f # https://oeis.org/A001622
SQRT1_2 = 0.7071067811865476f # The square root of 1/2
SQRT2 = 1.4142135623730951f # The square root of 2, https://oeis.org/A002193
SQRT_E = 1.6487212707001282f # https://oeis.org/A019774
SQRT_PI = 1.7724538509055159f # https://oeis.org/A002161
SQRT_PHI = 1.272019649514069f # https://oeis.org/A139339
EPSILON = 2.220446049250313e-16f # The difference between 1 and the smallest floating point number greater than 1, formula: 7/3 - 4/3 - 1
MIN_FLOAT_VALUE = 4.9406564584124654417656879286822137236505980e-324 # Lowest value of float
MAX_FLOAT_VALUE = 1.79769313486231570814527423731704356798070e+308 # Highest value of float
HALF_PI = 1.5707963267948966f # PI / 2
TAU = 6.283185307179586f # 2 * PI
# `Int`/`Float` bare-wrap into `Number` with no wrapper syntax at all (see
# `plum-checker`'s `unify`/`monomorphize::wrapPrimitiveAgainstExpected`) —
# assigning/returning/passing a bare `Int`/`Float` value wherever `Number` is
# expected just works. A method not found on `Int`/`Float` directly (they no
# longer have their own method tables at all) falls back to `Number`'s,
# boxing `self` first (see `plum-checker`/`plum-wasm-codegen`'s matching
# `AttrKind::Method` dispatch fallback) — so `x.abs()` for a bare `Int`/
# `Float` `x` dispatches here exactly as if `Int`/`Float` still had their own
# `abs` method.
enum Number =
| Int
| Float
fun kind(self) -> Str =
match self
Int(_) => "Int"
Float(_) => "Float"
fun toFloatValue(self) -> Float =
match self
Int(i) => Float(i)
Float(f) => f
fun abs(self) -> Number =
match self
Int(i) => Int(i < 0 ? -i : i)
Float(f) => Float(f < 0.0f ? -f : f)
# Preserves 0.0 / -0.0 / NaN for the `Float` case, matching Go's
# `math.Signbit`-adjacent `Copysign`/`sign` conventions loosely.
fun sign(self) -> Number =
match self
Int(i) => Int(i > 0 ? 1 : i < 0 ? -1 : 0)
Float(f) =>
if f > 0.0f
return Float(1.0f)
if f < 0.0f
return Float(-1.0f)
return Float(f)
# An `Int` already IS its own well-distributed bit pattern — used by
# `Map[K: Hashable, V]` to bucket `Int` keys. A `Float` hashes via a
# truncating conversion (no bit-level float hashing attempted).
fun hash(self) -> Int =
match self
Int(i) => i
Float(f) => Int(f)
# An Int is always already whole, so trunc/floor/ceil/round are all just
# the value itself widened to Float.
fun trunc(self) -> Float =
match self
Int(i) => Float(i)
# `Int(f)` truncates toward zero (see `plum-wasm-codegen`'s `Int(x)`
# cross-type conversion) — exactly `trunc`'s definition.
Float(f) => Float(Int(f))
fun floor(self) -> Float =
match self
Int(i) => Float(i)
Float(f) =>
t := Float(Int(f))
if f < 0.0f && t != f
return t - 1.0f
return t
fun ceil(self) -> Float =
match self
Int(i) => Float(i)
Float(f) =>
t := Float(Int(f))
if f > 0.0f && t != f
return t + 1.0f
return t
# Round-half-away-from-zero (matching Go's `math.Round`), not
# round-half-to-even.
fun round(self) -> Float =
match self
Int(i) => Float(i)
Float(f) =>
neg := f < 0.0f
v := neg ? -f : f
r := {v + 0.5f}.floor()
return neg ? -r : r
fun log(self) -> Float =
ln(self.toFloatValue())
fun log2(self) -> Float =
ln(self.toFloatValue()) * LOG2E
fun log10(self) -> Float =
ln(self.toFloatValue()) * LOG10E
# floor(log2(|self|)). An `Int` self computes this exactly with integer
# division rather than through Float rounding error; a `Float` self falls
# back to the real logarithm.
fun logb(self) -> Float =
match self
Int(i) =>
if i == 0
return -1.0f / 0.0f
n := i < 0 ? -i : i
k := 0
while n >= 2
n = n / 2
k = k + 1
return Float(k)
Float(f) =>
if f == 0.0f
return -1.0f / 0.0f
return {ln(f < 0.0f ? -f : f) * LOG2E}.floor()
fun sqrt(self) -> Float =
sqrt(self.toFloatValue())
# self^y. Mirrors the original `Int.pow`/`Float.pow`: a whole-number `y`
# (including negative) is computed exactly by repeated squaring; a
# fractional `y` falls back to self^y = exp(y * ln(self)), which requires
# self > 0.
fun pow(self, y: Float) -> Float =
powFloat(self.toFloatValue(), y)
# Check whether this number is finite, ie not +/-infinity and not NaN.
fun isFinite(self) -> Bool =
!self.isNaN() && !self.isInfinite()
fun isInfinite(self) -> Bool =
match self
Int(_) => False
Float(f) => f > MAX_FLOAT_VALUE || f < -MAX_FLOAT_VALUE
fun isNaN(self) -> Bool =
match self
Int(_) => False
Float(f) => f != f
fun min(self, other: Number) -> Number =
self.toFloatValue() < other.toFloatValue() ? self : other
fun max(self, other: Number) -> Number =
self.toFloatValue() > other.toFloatValue() ? self : other
# Inverse hyperbolic cosine, via acosh(x) = ln(x + sqrt(x^2 - 1)), x >= 1.
fun acosh(self) -> Float =
v := self.toFloatValue()
if v < 1.0f
return 0.0f / 0.0f
return ln(v + sqrt(v * v - 1.0f))
fun sinh(self) -> Float =
v := self.toFloatValue()
return {exp(v) - exp(-v)} / 2.0f
fun cosh(self) -> Float =
v := self.toFloatValue()
return {exp(v) + exp(-v)} / 2.0f
fun tanh(self) -> Float =
return self.sinh() / self.cosh()
# Inverse hyperbolic sine: asinh(x) = ln(x + sqrt(x^2 + 1)).
fun asinh(self) -> Float =
v := self.toFloatValue()
return ln(v + sqrt(v * v + 1.0f))
# Inverse hyperbolic tangent: atanh(x) = 0.5*ln((1+x)/(1-x)), |x| < 1.
fun atanh(self) -> Float =
v := self.toFloatValue()
if v <= -1.0f || v >= 1.0f
return 0.0f / 0.0f
return 0.5f * ln({1.0f + v} / {1.0f - v})
# Rounded to 6 fractional digits, with trailing zeros trimmed (but always
# at least one digit after the point, e.g. `3.0` not `3.`), for a `Float`;
# plain decimal digits for an `Int`.
fun toStr(self) -> Str =
match self
Int(i) => intToStr(i)
Float(f) =>
if f != f
return "NaN"
if f > MAX_FLOAT_VALUE || f < -MAX_FLOAT_VALUE
return f < 0.0f ? "-Infinity" : "Infinity"
neg := f < 0.0f
abs_val := neg ? -f : f
int_part := Int(abs_val)
frac_val := abs_val - Float(int_part)
scaled := Int(frac_val * 1000000.0f + 0.5f)
carry := scaled >= 1000000
whole_part := carry ? int_part + 1 : int_part
frac := carry ? 0 : scaled
s := intToStr(whole_part) + "." + trimmedFracDigits(frac, 6)
return neg ? "-" + s : s
# ---- free-function helpers (no `self` to dispatch on) ----
# Plain decimal rendering of an `Int` — factored out of `Number.toStr` so the
# `Float` branch can reuse it (via `intToStr(whole_part)`) without
# re-dispatching back through `Number.toStr` itself.
fun intToStr(n: Int) -> Str =
if n == 0
return "0"
neg := n < 0
v := neg ? -n : n
s := digitsToStr(v)
return neg ? "-" + s : s
# Builds the decimal digits of a positive Int, most significant first, by
# peeling off one digit at a time from the least significant end (so the
# recursion emits digits in reverse call order — the leading, smallest-place
# digit is appended last).
fun digitsToStr(n: Int) -> Str =
if n == 0
return ""
return digitsToStr(n / 10) + digitChar(n % 10)
fun digitChar(d: Int) -> Str =
match d
0 => "0"
1 => "1"
2 => "2"
3 => "3"
4 => "4"
5 => "5"
6 => "6"
7 => "7"
8 => "8"
9 => "9"
_ => "0"
# Renders `n`'s digits most-significant-first, always emitting exactly as many
# digits as `place_value` has powers of ten (leading zeros included) — used to
# render a fractional part at a fixed width regardless of its own leading zeros.
fun fixedFracDigits(n: Int, place_value: Int) -> Str =
if place_value == 0
return ""
return digitChar(n / place_value % 10) + fixedFracDigits(n, place_value / 10)
fun pow10(n: Int) -> Int =
if n <= 0
return 1
return 10 * pow10(n - 1)
# `fixedFracDigits(n, pow10(digits - 1))`, minus however many trailing-zero
# digits `n` has — but always at least one digit ("0" if `n` is 0 outright).
fun trimmedFracDigits(n: Int, digits: Int) -> Str =
if n == 0 || digits <= 1
return digitChar(n % 10)
if n % 10 == 0
return trimmedFracDigits(n / 10, digits - 1)
return fixedFracDigits(n, pow10(digits - 1))
# Shared `base^y` algorithm behind `Number.pow`, extracted so both the `Int`
# and `Float` self cases (which only differ in how `base` was obtained) share
# one body.
fun powFloat(base: Float, y: Float) -> Float =
if y == 0.0f
return 1.0f
yi := Int(y)
if Float(yi) == y
neg := yi < 0
n := neg ? -yi : yi
result := 1.0f
b := base
m := n
while m > 0
if m % 2 == 1
result = result * b
b = b * b
m = m / 2
return neg ? 1.0f / result : result
if base < 0.0f
return 0.0f / 0.0f
return exp(y * ln(base))
# Parses a decimal integer, with an optional leading `+`/`-`.
fun parseInt(s: Str) -> Result[Int, Str] =
len := s.length()
if len == 0
return Err("empty string")
neg := s.byteAt(0) == 45
start := neg || s.byteAt(0) == 43 ? 1 : 0
if start >= len
return Err("invalid integer: '{s}'")
value := 0
i := start
while i < len
b := s.byteAt(i)
if b < 48 || b > 57
return Err("invalid integer: '{s}'")
value = value * 10 + b - 48
i = i + 1
return Ok(neg ? -value : value)
# Parses a decimal float, with an optional leading `+`/`-` and an optional
# `.` fractional part (no exponent notation).
fun parseFloat(s: Str) -> Result[Float, Str] =
len := s.length()
if len == 0
return Err("empty string")
neg := s.byteAt(0) == 45
start := neg || s.byteAt(0) == 43 ? 1 : 0
if start >= len
return Err("invalid float: '{s}'")
int_part := 0.0f
saw_digit := False
i := start
while i < len && s.byteAt(i) != 46
b := s.byteAt(i)
if b < 48 || b > 57
return Err("invalid float: '{s}'")
int_part = int_part * 10.0f + Float(b - 48)
saw_digit = True
i = i + 1
frac_part := 0.0f
frac_scale := 1.0f
if i < len && s.byteAt(i) == 46
i = i + 1
while i < len
b = s.byteAt(i)
if b < 48 || b > 57
return Err("invalid float: '{s}'")
frac_scale = frac_scale / 10.0f
frac_part = frac_part + Float(b - 48) * frac_scale
saw_digit = True
i = i + 1
if !saw_digit
return Err("invalid float: '{s}'")
value := int_part + frac_part
return Ok(neg ? -value : value)
# A raw, host-provided pseudo-random 64-bit Int (xorshift64*, seeded from the
# wall clock — not cryptographically secure). Every other random-number
# function in std is built on top of this one host extern.
extern fun rawRandomInt() -> Int
# A random Float uniformly distributed in [0.0, 1.0).
fun random() -> Float =
return Float(rawRandomInt() & MAX_VALUE) / Float(MAX_VALUE)
# A random Int uniformly distributed in [0, n). Returns 0 for n <= 0.
fun randomInt(n: Int) -> Int =
if n <= 0
return 0
return { rawRandomInt() & MAX_VALUE } % n
# Natural exponential, e^x, via range reduction (halve x until |x| <= 0.5,
# run the Taylor series there where it converges fast, then square the
# result back up the same number of halvings).
fun exp(x: Float) -> Float =
if x != x
return x
if x > 700.0f
return 1.0f / 0.0f
if x < -700.0f
return 0.0f
v := x
k := 0
while v > 0.5f || v < -0.5f
v = v / 2.0f
k = k + 1
term := 1.0f
sum := 1.0f
n := 1
while n < 25
term = term * v / Float(n)
sum = sum + term
n = n + 1
result := sum
i := 0
while i < k
result = result * result
i = i + 1
return result
# Natural logarithm, via range reduction to v in [1, 2) plus the
# fast-converging series ln(v) = 2*atanh((v-1)/(v+1)).
fun ln(x: Float) -> Float =
if x != x || x < 0.0f
return 0.0f / 0.0f
if x == 0.0f
return -1.0f / 0.0f
if x > MAX_FLOAT_VALUE
return x
v := x
k := 0
while v >= 2.0f
v = v / 2.0f
k = k + 1
while v < 1.0f
v = v * 2.0f
k = k - 1
t := {v - 1.0f} / {v + 1.0f}
t2 := t * t
term := t
sum := t
n := 1
while n < 30
term = term * t2
sum = sum + term / Float(2 * n + 1)
n = n + 1
return Float(k) * LN2 + 2.0f * sum
# Square root via Newton's method, iterating to a fixed point.
fun sqrt(x: Float) -> Float =
if x < 0.0f
return 0.0f / 0.0f
if x == 0.0f || x != x
return x
guess := x
prev := 0.0f
i := 0
while guess != prev && i < 100
prev = guess
guess = 0.5f * {guess + x / guess}
i = i + 1
return guess
# Reduces `x` into roughly `[-PI, PI]` by subtracting the nearest multiple of
# `TAU` — the range `sin`/`cos`'s Taylor series below actually converge
# quickly over. For very large `|x|` (many multiples of `TAU`), floating-point
# cancellation in `x - k*TAU` loses precision the same way any naive
# range-reduction by subtraction does; a real libm uses extended-precision
# constants to avoid this, which isn't attempted here.
fun reduceToPi(x: Float) -> Float =
k := Int(x / TAU + {x >= 0.0f ? 0.5f : -0.5f})
return x - Float(k) * TAU
fun sin(x: Float) -> Float =
if x != x || x > MAX_FLOAT_VALUE || x < -MAX_FLOAT_VALUE
return 0.0f / 0.0f
r := reduceToPi(x)
r2 := r * r
term := r
sum := r
n := 1
while n < 10
term = term * {-r2} / Float({2 * n} * {2 * n + 1})
sum = sum + term
n = n + 1
return sum
fun cos(x: Float) -> Float =
if x != x || x > MAX_FLOAT_VALUE || x < -MAX_FLOAT_VALUE
return 0.0f / 0.0f
return sin(x + HALF_PI)
fun tan(x: Float) -> Float =
return sin(x) / cos(x)
# Arctangent, via repeated argument-halving (`atan(x) = 2*atan(x / (1 +
# sqrt(1+x^2)))`) until `|x| <= 0.5` (where the Taylor series below converges
# quickly), then doubling the result back up the same number of times.
fun atan(x: Float) -> Float =
if x != x
return x
neg := x < 0.0f
v := neg ? -x : x
k := 0
while v > 0.5f && k < 8
v = v / {1.0f + sqrt(1.0f + v * v)}
k = k + 1
v2 := v * v
term := v
sum := v
n := 1
while n < 20
term = term * {-v2}
sum = sum + term / Float(2 * n + 1)
n = n + 1
scale := Float(1 << k)
result := sum * scale
return neg ? -result : result
fun asin(x: Float) -> Float =
if x != x || x < -1.0f || x > 1.0f
return 0.0f / 0.0f
if x == 1.0f
return HALF_PI
if x == -1.0f
return -HALF_PI
return atan(x / sqrt(1.0f - x * x))
fun acos(x: Float) -> Float =
return HALF_PI - asin(x)
# Angle (in radians) of the point `(x, y)` from the origin, in the correct
# quadrant for any sign combination of `x`/`y` (unlike plain `atan(y/x)`,
# which can't distinguish opposite quadrants).
fun atan2(y: Float, x: Float) -> Float =
if x > 0.0f
return atan(y / x)
if x < 0.0f && y >= 0.0f
return atan(y / x) + PI
if x < 0.0f && y < 0.0f
return atan(y / x) - PI
if x == 0.0f && y > 0.0f
return HALF_PI
if x == 0.0f && y < 0.0f
return -HALF_PI
return 0.0f
fun hypot(a: Float, b: Float) -> Float =
return sqrt(a * a + b * b)
# Cube root via Newton's method (fixed iteration count, unlike `sqrt`'s
# converge-to-a-fixed-point loop, since the cubic update step doesn't reach an
# exact fixed point in float precision as reliably as the quadratic one does).
fun cbrt(x: Float) -> Float =
if x == 0.0f || x != x
return x
neg := x < 0.0f
v := neg ? -x : x
guess := v
i := 0
while i < 60
guess = {2.0f * guess + v / {guess * guess}} / 3.0f
i = i + 1
return neg ? -guess : guess