plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
plum-examples/numbers.plum
| ca5fd6f | 1 | import std/Bool |
| ca5fd6f | 2 | import std/Str |
| ca5fd6f | 3 | import std/Number |
| ca5fd6f | 4 | |
| ca5fd6f | 5 | # Regression coverage for `plum-std/Number.plum` — the methods it now hosts |
| ca5fd6f | 6 | # for BOTH `Int` and `Float` (via `match self`), after `Int.plum`/`Float.plum` |
| ca5fd6f | 7 | # were folded into it and deleted. Every call below reaches its method |
| ca5fd6f | 8 | # through the primitive-to-wrapping-enum dispatch fallback (`Int`/`Float` |
| ca5fd6f | 9 | # have no method tables of their own any more). |
| ca5fd6f | 10 | |
| ca5fd6f | 11 | fun toInt(n: Number) -> Int = |
| ca5fd6f | 12 | match n |
| ca5fd6f | 13 | Int(i) => i |
| ca5fd6f | 14 | Float(f) => Int(f) |
| ca5fd6f | 15 | |
| ca5fd6f | 16 | test "abs works for both Int and Float, wrapping back into the same variant" |
| ca5fd6f | 17 | assert {-5}.abs().kind() == "Int" |
| ca5fd6f | 18 | assert {-2.5}.abs().kind() == "Float" |
| ca5fd6f | 19 | |
| ca5fd6f | 20 | test "sign returns -1/0/1 (or the matching Float) for both Int and Float" |
| ca5fd6f | 21 | assert toInt({5}.sign()) == 1 |
| ca5fd6f | 22 | assert toInt({-5}.sign()) == -1 |
| ca5fd6f | 23 | assert toInt({0}.sign()) == 0 |
| ca5fd6f | 24 | |
| ca5fd6f | 25 | test "hash is identity for Int and truncating for Float" |
| ca5fd6f | 26 | assert {5}.hash() == 5 |
| ca5fd6f | 27 | assert {5.9}.hash() == 5 |
| ca5fd6f | 28 | |
| ca5fd6f | 29 | test "trunc/floor/ceil/round agree with Int passthrough and real Float math" |
| ca5fd6f | 30 | assert {5}.trunc() == 5.0f |
| ca5fd6f | 31 | assert {2.7}.trunc() == 2.0f |
| ca5fd6f | 32 | assert {-2.7}.trunc() == -2.0f |
| ca5fd6f | 33 | assert {2.7}.floor() == 2.0f |
| ca5fd6f | 34 | assert {-2.7}.floor() == -3.0f |
| ca5fd6f | 35 | assert {2.3}.ceil() == 3.0f |
| ca5fd6f | 36 | assert {-2.3}.ceil() == -2.0f |
| ca5fd6f | 37 | assert {2.5}.round() == 3.0f |
| ca5fd6f | 38 | assert {-2.5}.round() == -3.0f |
| ca5fd6f | 39 | |
| ca5fd6f | 40 | test "sqrt works for Int (via Float conversion) and Float directly" |
| ca5fd6f | 41 | assert {4}.sqrt() == 2.0f |
| ca5fd6f | 42 | assert {2.25}.sqrt() == 1.5f |
| ca5fd6f | 43 | |
| ca5fd6f | 44 | test "pow computes integer powers exactly for both Int and Float self" |
| ca5fd6f | 45 | assert {2}.pow(10.0f) == 1024.0f |
| ca5fd6f | 46 | assert {2.0}.pow(10.0f) == 1024.0f |
| ca5fd6f | 47 | |
| ca5fd6f | 48 | test "log2/log10 agree between Int and the equivalent Float" |
| ca5fd6f | 49 | # `log`/`log2`/`log10` are built on the Taylor-series `ln` approximation |
| ca5fd6f | 50 | # (see `plum-std/Number.plum`'s `ln`), not exact libm — compare within a |
| ca5fd6f | 51 | # small epsilon rather than requiring bit-exact equality. |
| ca5fd6f | 52 | a := {8}.log2() |
| ca5fd6f | 53 | b := {8.0}.log2() |
| ca5fd6f | 54 | assert {a - b}.abs().toFloatValue() < 0.0001f |
| ca5fd6f | 55 | c := {100}.log10() |
| ca5fd6f | 56 | d := {100.0}.log10() |
| ca5fd6f | 57 | assert {c - d}.abs().toFloatValue() < 0.0001f |
| ca5fd6f | 58 | |
| ca5fd6f | 59 | test "isFinite/isInfinite/isNaN are always well-defined for Int, real for Float" |
| ca5fd6f | 60 | assert {5}.isFinite() |
| ca5fd6f | 61 | assert !{5}.isInfinite() |
| ca5fd6f | 62 | assert !{5}.isNaN() |
| ca5fd6f | 63 | assert {1.0f / 0.0f}.isInfinite() |
| ca5fd6f | 64 | assert {0.0f / 0.0f}.isNaN() |
| ca5fd6f | 65 | assert {1.5}.isFinite() |
| ca5fd6f | 66 | |
| ca5fd6f | 67 | test "min/max compare across Int and Float without losing the original variant" |
| ca5fd6f | 68 | assert {3}.min(5).kind() == "Int" |
| ca5fd6f | 69 | assert {3}.max(2.5).kind() == "Int" |
| ca5fd6f | 70 | assert {2.5}.min(3).kind() == "Float" |
| ca5fd6f | 71 | |
| ca5fd6f | 72 | test "toStr renders Int and Float correctly, including negatives and fractions" |
| ca5fd6f | 73 | assert {42}.toStr() == "42" |
| ca5fd6f | 74 | assert {-7}.toStr() == "-7" |
| ca5fd6f | 75 | assert {0}.toStr() == "0" |
| ca5fd6f | 76 | assert {3.5}.toStr() == "3.5" |
| ca5fd6f | 77 | assert {-3.5}.toStr() == "-3.5" |
| ca5fd6f | 78 | |
| ca5fd6f | 79 | test "parseInt/parseFloat round-trip a rendered number" |
| ca5fd6f | 80 | assert parseInt("123").unwrapOr(-1) == 123 |
| ca5fd6f | 81 | assert parseInt("-45").unwrapOr(1) == -45 |
| ca5fd6f | 82 | assert parseFloat("3.25").unwrapOr(-1.0f) == 3.25f |
| ca5fd6f | 83 | |
| ca5fd6f | 84 | test "trig/hyperbolic/exp/ln free functions and Number methods still work post-migration" |
| ca5fd6f | 85 | assert sin(0.0f) == 0.0f |
| ca5fd6f | 86 | assert cos(0.0f) == 1.0f |
| ca5fd6f | 87 | assert {0}.sinh() == 0.0f |
| ca5fd6f | 88 | assert {0}.cosh() == 1.0f |
| ca5fd6f | 89 | |
| ca5fd6f | 90 | test "string interpolation of a Float value works, via Number.toStr" |
| ca5fd6f | 91 | x := 3.5f |
| ca5fd6f | 92 | assert "value is {x}" == "value is 3.5" |
| ca5fd6f | 93 | y := {8}.log2() |
| ca5fd6f | 94 | assert "computed is {y}" == "computed is 3.0" |
| ca5fd6f | 95 | |
| ca5fd6f | 96 | test "assert of two directly-chained Float method calls renders a real failure message" |
| ca5fd6f | 97 | # Previously crashed the whole compile with "interpolating a Float value is |
| ca5fd6f | 98 | # not yet supported" instead of a normal pass/fail report — `looksLikeFloat` |
| ca5fd6f | 99 | # only caught an OBVIOUS float literal on either side of the comparison, |
| ca5fd6f | 100 | # missing any Float-typed expression that wasn't written as one (like these |
| ca5fd6f | 101 | # chained method calls). Fixed at the root: `"{expr}"` interpolation of a |
| ca5fd6f | 102 | # Float now genuinely works (via `Number.toStr`), so the "Expected/Actual" |
| ca5fd6f | 103 | # failure-message machinery no longer needs to special-case Float at all. |
| ca5fd6f | 104 | assert {8}.log2() == {8.0}.log2() |