plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
plum-examples/numbers.plum
import std/Bool
import std/Str
import std/Number
# Regression coverage for `plum-std/Number.plum` — the methods it now hosts
# for BOTH `Int` and `Float` (via `match self`), after `Int.plum`/`Float.plum`
# were folded into it and deleted. Every call below reaches its method
# through the primitive-to-wrapping-enum dispatch fallback (`Int`/`Float`
# have no method tables of their own any more).
fun toInt(n: Number) -> Int =
match n
Int(i) => i
Float(f) => Int(f)
test "abs works for both Int and Float, wrapping back into the same variant"
assert {-5}.abs().kind() == "Int"
assert {-2.5}.abs().kind() == "Float"
test "sign returns -1/0/1 (or the matching Float) for both Int and Float"
assert toInt({5}.sign()) == 1
assert toInt({-5}.sign()) == -1
assert toInt({0}.sign()) == 0
test "hash is identity for Int and truncating for Float"
assert {5}.hash() == 5
assert {5.9}.hash() == 5
test "trunc/floor/ceil/round agree with Int passthrough and real Float math"
assert {5}.trunc() == 5.0f
assert {2.7}.trunc() == 2.0f
assert {-2.7}.trunc() == -2.0f
assert {2.7}.floor() == 2.0f
assert {-2.7}.floor() == -3.0f
assert {2.3}.ceil() == 3.0f
assert {-2.3}.ceil() == -2.0f
assert {2.5}.round() == 3.0f
assert {-2.5}.round() == -3.0f
test "sqrt works for Int (via Float conversion) and Float directly"
assert {4}.sqrt() == 2.0f
assert {2.25}.sqrt() == 1.5f
test "pow computes integer powers exactly for both Int and Float self"
assert {2}.pow(10.0f) == 1024.0f
assert {2.0}.pow(10.0f) == 1024.0f
test "log2/log10 agree between Int and the equivalent Float"
# `log`/`log2`/`log10` are built on the Taylor-series `ln` approximation
# (see `plum-std/Number.plum`'s `ln`), not exact libm — compare within a
# small epsilon rather than requiring bit-exact equality.
a := {8}.log2()
b := {8.0}.log2()
assert {a - b}.abs().toFloatValue() < 0.0001f
c := {100}.log10()
d := {100.0}.log10()
assert {c - d}.abs().toFloatValue() < 0.0001f
test "isFinite/isInfinite/isNaN are always well-defined for Int, real for Float"
assert {5}.isFinite()
assert !{5}.isInfinite()
assert !{5}.isNaN()
assert {1.0f / 0.0f}.isInfinite()
assert {0.0f / 0.0f}.isNaN()
assert {1.5}.isFinite()
test "min/max compare across Int and Float without losing the original variant"
assert {3}.min(5).kind() == "Int"
assert {3}.max(2.5).kind() == "Int"
assert {2.5}.min(3).kind() == "Float"
test "toStr renders Int and Float correctly, including negatives and fractions"
assert {42}.toStr() == "42"
assert {-7}.toStr() == "-7"
assert {0}.toStr() == "0"
assert {3.5}.toStr() == "3.5"
assert {-3.5}.toStr() == "-3.5"
test "parseInt/parseFloat round-trip a rendered number"
assert parseInt("123").unwrapOr(-1) == 123
assert parseInt("-45").unwrapOr(1) == -45
assert parseFloat("3.25").unwrapOr(-1.0f) == 3.25f
test "trig/hyperbolic/exp/ln free functions and Number methods still work post-migration"
assert sin(0.0f) == 0.0f
assert cos(0.0f) == 1.0f
assert {0}.sinh() == 0.0f
assert {0}.cosh() == 1.0f
test "string interpolation of a Float value works, via Number.toStr"
x := 3.5f
assert "value is {x}" == "value is 3.5"
y := {8}.log2()
assert "computed is {y}" == "computed is 3.0"
test "assert of two directly-chained Float method calls renders a real failure message"
# Previously crashed the whole compile with "interpolating a Float value is
# not yet supported" instead of a normal pass/fail report — `looksLikeFloat`
# only caught an OBVIOUS float literal on either side of the comparison,
# missing any Float-typed expression that wasn't written as one (like these
# chained method calls). Fixed at the root: `"{expr}"` interpolation of a
# Float now genuinely works (via `Number.toStr`), so the "Expected/Actual"
# failure-message machinery no longer needs to special-case Float at all.
assert {8}.log2() == {8.0}.log2()