plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
plum-std/Number.plum
| ca5fd6f | 1 | module std |
| ca5fd6f | 2 | import std/Str |
| ca5fd6f | 3 | import std/Bool |
| ca5fd6f | 4 | import std/Result |
| ca5fd6f | 5 | |
| ca5fd6f | 6 | MIN_VALUE = -0x8000_0000_0000_0000 # Lowest value of Int |
| ca5fd6f | 7 | MAX_VALUE = 0x7FFF_FFFF_FFFF_FFFF # Highest value of Int |
| ca5fd6f | 8 | LARGE = 1 << 28 # 2**28 |
| ca5fd6f | 9 | |
| ca5fd6f | 10 | E = 2.718281828459045f # Euler's number, the base of natural logarithms, e, https://oeis.org/A001113 |
| ca5fd6f | 11 | LN10 = 2.302585092994046f # The natural logarithm of 10, https://oeis.org/A002392 |
| ca5fd6f | 12 | LN2 = 0.6931471805599453f # The natural logarithm of 2, https://oeis.org/A002162 |
| ca5fd6f | 13 | LOG10E = 0.4342944819032518f # The base 10 logarithm of e, formula: 1 / LN10 |
| ca5fd6f | 14 | LOG2E = 1.4426950408889634f # The base 2 logarithm of e, formula: 1 / LN2 |
| ca5fd6f | 15 | PI = 3.141592653589793f # The ratio of the circumference of a circle to its diameter, https://oeis.org/A000796 |
| ca5fd6f | 16 | PHI = 1.618033988749895f # https://oeis.org/A001622 |
| ca5fd6f | 17 | SQRT1_2 = 0.7071067811865476f # The square root of 1/2 |
| ca5fd6f | 18 | SQRT2 = 1.4142135623730951f # The square root of 2, https://oeis.org/A002193 |
| ca5fd6f | 19 | SQRT_E = 1.6487212707001282f # https://oeis.org/A019774 |
| ca5fd6f | 20 | SQRT_PI = 1.7724538509055159f # https://oeis.org/A002161 |
| ca5fd6f | 21 | SQRT_PHI = 1.272019649514069f # https://oeis.org/A139339 |
| ca5fd6f | 22 | EPSILON = 2.220446049250313e-16f # The difference between 1 and the smallest floating point number greater than 1, formula: 7/3 - 4/3 - 1 |
| ca5fd6f | 23 | MIN_FLOAT_VALUE = 4.9406564584124654417656879286822137236505980e-324 # Lowest value of float |
| ca5fd6f | 24 | MAX_FLOAT_VALUE = 1.79769313486231570814527423731704356798070e+308 # Highest value of float |
| ca5fd6f | 25 | HALF_PI = 1.5707963267948966f # PI / 2 |
| ca5fd6f | 26 | TAU = 6.283185307179586f # 2 * PI |
| ca5fd6f | 27 | |
| ca5fd6f | 28 | # `Int`/`Float` bare-wrap into `Number` with no wrapper syntax at all (see |
| ca5fd6f | 29 | # `plum-checker`'s `unify`/`monomorphize::wrapPrimitiveAgainstExpected`) — |
| ca5fd6f | 30 | # assigning/returning/passing a bare `Int`/`Float` value wherever `Number` is |
| ca5fd6f | 31 | # expected just works. A method not found on `Int`/`Float` directly (they no |
| ca5fd6f | 32 | # longer have their own method tables at all) falls back to `Number`'s, |
| ca5fd6f | 33 | # boxing `self` first (see `plum-checker`/`plum-wasm-codegen`'s matching |
| ca5fd6f | 34 | # `AttrKind::Method` dispatch fallback) — so `x.abs()` for a bare `Int`/ |
| ca5fd6f | 35 | # `Float` `x` dispatches here exactly as if `Int`/`Float` still had their own |
| ca5fd6f | 36 | # `abs` method. |
| ca5fd6f | 37 | enum Number = |
| ca5fd6f | 38 | | Int |
| ca5fd6f | 39 | | Float |
| ca5fd6f | 40 | |
| ca5fd6f | 41 | fun kind(self) -> Str = |
| ca5fd6f | 42 | match self |
| ca5fd6f | 43 | Int(_) => "Int" |
| ca5fd6f | 44 | Float(_) => "Float" |
| ca5fd6f | 45 | |
| ca5fd6f | 46 | fun toFloatValue(self) -> Float = |
| ca5fd6f | 47 | match self |
| ca5fd6f | 48 | Int(i) => Float(i) |
| ca5fd6f | 49 | Float(f) => f |
| ca5fd6f | 50 | |
| ca5fd6f | 51 | fun abs(self) -> Number = |
| ca5fd6f | 52 | match self |
| ca5fd6f | 53 | Int(i) => Int(i < 0 ? -i : i) |
| ca5fd6f | 54 | Float(f) => Float(f < 0.0f ? -f : f) |
| ca5fd6f | 55 | |
| ca5fd6f | 56 | # Preserves 0.0 / -0.0 / NaN for the `Float` case, matching Go's |
| ca5fd6f | 57 | # `math.Signbit`-adjacent `Copysign`/`sign` conventions loosely. |
| ca5fd6f | 58 | fun sign(self) -> Number = |
| ca5fd6f | 59 | match self |
| ca5fd6f | 60 | Int(i) => Int(i > 0 ? 1 : i < 0 ? -1 : 0) |
| ca5fd6f | 61 | Float(f) => |
| ca5fd6f | 62 | if f > 0.0f |
| ca5fd6f | 63 | return Float(1.0f) |
| ca5fd6f | 64 | if f < 0.0f |
| ca5fd6f | 65 | return Float(-1.0f) |
| ca5fd6f | 66 | return Float(f) |
| ca5fd6f | 67 | |
| ca5fd6f | 68 | # An `Int` already IS its own well-distributed bit pattern — used by |
| ca5fd6f | 69 | # `Map[K: Hashable, V]` to bucket `Int` keys. A `Float` hashes via a |
| ca5fd6f | 70 | # truncating conversion (no bit-level float hashing attempted). |
| ca5fd6f | 71 | fun hash(self) -> Int = |
| ca5fd6f | 72 | match self |
| ca5fd6f | 73 | Int(i) => i |
| ca5fd6f | 74 | Float(f) => Int(f) |
| ca5fd6f | 75 | |
| ca5fd6f | 76 | # An Int is always already whole, so trunc/floor/ceil/round are all just |
| ca5fd6f | 77 | # the value itself widened to Float. |
| ca5fd6f | 78 | fun trunc(self) -> Float = |
| ca5fd6f | 79 | match self |
| ca5fd6f | 80 | Int(i) => Float(i) |
| ca5fd6f | 81 | # `Int(f)` truncates toward zero (see `plum-wasm-codegen`'s `Int(x)` |
| ca5fd6f | 82 | # cross-type conversion) — exactly `trunc`'s definition. |
| ca5fd6f | 83 | Float(f) => Float(Int(f)) |
| ca5fd6f | 84 | |
| ca5fd6f | 85 | fun floor(self) -> Float = |
| ca5fd6f | 86 | match self |
| ca5fd6f | 87 | Int(i) => Float(i) |
| ca5fd6f | 88 | Float(f) => |
| ca5fd6f | 89 | t := Float(Int(f)) |
| ca5fd6f | 90 | if f < 0.0f && t != f |
| ca5fd6f | 91 | return t - 1.0f |
| ca5fd6f | 92 | return t |
| ca5fd6f | 93 | |
| ca5fd6f | 94 | fun ceil(self) -> Float = |
| ca5fd6f | 95 | match self |
| ca5fd6f | 96 | Int(i) => Float(i) |
| ca5fd6f | 97 | Float(f) => |
| ca5fd6f | 98 | t := Float(Int(f)) |
| ca5fd6f | 99 | if f > 0.0f && t != f |
| ca5fd6f | 100 | return t + 1.0f |
| ca5fd6f | 101 | return t |
| ca5fd6f | 102 | |
| ca5fd6f | 103 | # Round-half-away-from-zero (matching Go's `math.Round`), not |
| ca5fd6f | 104 | # round-half-to-even. |
| ca5fd6f | 105 | fun round(self) -> Float = |
| ca5fd6f | 106 | match self |
| ca5fd6f | 107 | Int(i) => Float(i) |
| ca5fd6f | 108 | Float(f) => |
| ca5fd6f | 109 | neg := f < 0.0f |
| ca5fd6f | 110 | v := neg ? -f : f |
| ca5fd6f | 111 | r := {v + 0.5f}.floor() |
| ca5fd6f | 112 | return neg ? -r : r |
| ca5fd6f | 113 | |
| ca5fd6f | 114 | fun log(self) -> Float = |
| ca5fd6f | 115 | ln(self.toFloatValue()) |
| ca5fd6f | 116 | |
| ca5fd6f | 117 | fun log2(self) -> Float = |
| ca5fd6f | 118 | ln(self.toFloatValue()) * LOG2E |
| ca5fd6f | 119 | |
| ca5fd6f | 120 | fun log10(self) -> Float = |
| ca5fd6f | 121 | ln(self.toFloatValue()) * LOG10E |
| ca5fd6f | 122 | |
| ca5fd6f | 123 | # floor(log2(|self|)). An `Int` self computes this exactly with integer |
| ca5fd6f | 124 | # division rather than through Float rounding error; a `Float` self falls |
| ca5fd6f | 125 | # back to the real logarithm. |
| ca5fd6f | 126 | fun logb(self) -> Float = |
| ca5fd6f | 127 | match self |
| ca5fd6f | 128 | Int(i) => |
| ca5fd6f | 129 | if i == 0 |
| ca5fd6f | 130 | return -1.0f / 0.0f |
| ca5fd6f | 131 | n := i < 0 ? -i : i |
| ca5fd6f | 132 | k := 0 |
| ca5fd6f | 133 | while n >= 2 |
| ca5fd6f | 134 | n = n / 2 |
| ca5fd6f | 135 | k = k + 1 |
| ca5fd6f | 136 | return Float(k) |
| ca5fd6f | 137 | Float(f) => |
| ca5fd6f | 138 | if f == 0.0f |
| ca5fd6f | 139 | return -1.0f / 0.0f |
| ca5fd6f | 140 | return {ln(f < 0.0f ? -f : f) * LOG2E}.floor() |
| ca5fd6f | 141 | |
| ca5fd6f | 142 | fun sqrt(self) -> Float = |
| ca5fd6f | 143 | sqrt(self.toFloatValue()) |
| ca5fd6f | 144 | |
| ca5fd6f | 145 | # self^y. Mirrors the original `Int.pow`/`Float.pow`: a whole-number `y` |
| ca5fd6f | 146 | # (including negative) is computed exactly by repeated squaring; a |
| ca5fd6f | 147 | # fractional `y` falls back to self^y = exp(y * ln(self)), which requires |
| ca5fd6f | 148 | # self > 0. |
| ca5fd6f | 149 | fun pow(self, y: Float) -> Float = |
| ca5fd6f | 150 | powFloat(self.toFloatValue(), y) |
| ca5fd6f | 151 | |
| ca5fd6f | 152 | # Check whether this number is finite, ie not +/-infinity and not NaN. |
| ca5fd6f | 153 | fun isFinite(self) -> Bool = |
| ca5fd6f | 154 | !self.isNaN() && !self.isInfinite() |
| ca5fd6f | 155 | |
| ca5fd6f | 156 | fun isInfinite(self) -> Bool = |
| ca5fd6f | 157 | match self |
| ca5fd6f | 158 | Int(_) => False |
| ca5fd6f | 159 | Float(f) => f > MAX_FLOAT_VALUE || f < -MAX_FLOAT_VALUE |
| ca5fd6f | 160 | |
| ca5fd6f | 161 | fun isNaN(self) -> Bool = |
| ca5fd6f | 162 | match self |
| ca5fd6f | 163 | Int(_) => False |
| ca5fd6f | 164 | Float(f) => f != f |
| ca5fd6f | 165 | |
| ca5fd6f | 166 | fun min(self, other: Number) -> Number = |
| ca5fd6f | 167 | self.toFloatValue() < other.toFloatValue() ? self : other |
| ca5fd6f | 168 | |
| ca5fd6f | 169 | fun max(self, other: Number) -> Number = |
| ca5fd6f | 170 | self.toFloatValue() > other.toFloatValue() ? self : other |
| ca5fd6f | 171 | |
| ca5fd6f | 172 | # Inverse hyperbolic cosine, via acosh(x) = ln(x + sqrt(x^2 - 1)), x >= 1. |
| ca5fd6f | 173 | fun acosh(self) -> Float = |
| ca5fd6f | 174 | v := self.toFloatValue() |
| ca5fd6f | 175 | if v < 1.0f |
| ca5fd6f | 176 | return 0.0f / 0.0f |
| ca5fd6f | 177 | return ln(v + sqrt(v * v - 1.0f)) |
| ca5fd6f | 178 | |
| ca5fd6f | 179 | fun sinh(self) -> Float = |
| ca5fd6f | 180 | v := self.toFloatValue() |
| ca5fd6f | 181 | return {exp(v) - exp(-v)} / 2.0f |
| ca5fd6f | 182 | |
| ca5fd6f | 183 | fun cosh(self) -> Float = |
| ca5fd6f | 184 | v := self.toFloatValue() |
| ca5fd6f | 185 | return {exp(v) + exp(-v)} / 2.0f |
| ca5fd6f | 186 | |
| ca5fd6f | 187 | fun tanh(self) -> Float = |
| ca5fd6f | 188 | return self.sinh() / self.cosh() |
| ca5fd6f | 189 | |
| ca5fd6f | 190 | # Inverse hyperbolic sine: asinh(x) = ln(x + sqrt(x^2 + 1)). |
| ca5fd6f | 191 | fun asinh(self) -> Float = |
| ca5fd6f | 192 | v := self.toFloatValue() |
| ca5fd6f | 193 | return ln(v + sqrt(v * v + 1.0f)) |
| ca5fd6f | 194 | |
| ca5fd6f | 195 | # Inverse hyperbolic tangent: atanh(x) = 0.5*ln((1+x)/(1-x)), |x| < 1. |
| ca5fd6f | 196 | fun atanh(self) -> Float = |
| ca5fd6f | 197 | v := self.toFloatValue() |
| ca5fd6f | 198 | if v <= -1.0f || v >= 1.0f |
| ca5fd6f | 199 | return 0.0f / 0.0f |
| ca5fd6f | 200 | return 0.5f * ln({1.0f + v} / {1.0f - v}) |
| ca5fd6f | 201 | |
| ca5fd6f | 202 | # Rounded to 6 fractional digits, with trailing zeros trimmed (but always |
| ca5fd6f | 203 | # at least one digit after the point, e.g. `3.0` not `3.`), for a `Float`; |
| ca5fd6f | 204 | # plain decimal digits for an `Int`. |
| ca5fd6f | 205 | fun toStr(self) -> Str = |
| ca5fd6f | 206 | match self |
| ca5fd6f | 207 | Int(i) => intToStr(i) |
| ca5fd6f | 208 | Float(f) => |
| ca5fd6f | 209 | if f != f |
| ca5fd6f | 210 | return "NaN" |
| ca5fd6f | 211 | if f > MAX_FLOAT_VALUE || f < -MAX_FLOAT_VALUE |
| ca5fd6f | 212 | return f < 0.0f ? "-Infinity" : "Infinity" |
| ca5fd6f | 213 | neg := f < 0.0f |
| ca5fd6f | 214 | abs_val := neg ? -f : f |
| ca5fd6f | 215 | int_part := Int(abs_val) |
| ca5fd6f | 216 | frac_val := abs_val - Float(int_part) |
| ca5fd6f | 217 | scaled := Int(frac_val * 1000000.0f + 0.5f) |
| ca5fd6f | 218 | carry := scaled >= 1000000 |
| ca5fd6f | 219 | whole_part := carry ? int_part + 1 : int_part |
| ca5fd6f | 220 | frac := carry ? 0 : scaled |
| ca5fd6f | 221 | s := intToStr(whole_part) + "." + trimmedFracDigits(frac, 6) |
| ca5fd6f | 222 | return neg ? "-" + s : s |
| ca5fd6f | 223 | |
| ca5fd6f | 224 | # ---- free-function helpers (no `self` to dispatch on) ---- |
| ca5fd6f | 225 | |
| ca5fd6f | 226 | # Plain decimal rendering of an `Int` — factored out of `Number.toStr` so the |
| ca5fd6f | 227 | # `Float` branch can reuse it (via `intToStr(whole_part)`) without |
| ca5fd6f | 228 | # re-dispatching back through `Number.toStr` itself. |
| ca5fd6f | 229 | fun intToStr(n: Int) -> Str = |
| ca5fd6f | 230 | if n == 0 |
| ca5fd6f | 231 | return "0" |
| ca5fd6f | 232 | neg := n < 0 |
| ca5fd6f | 233 | v := neg ? -n : n |
| ca5fd6f | 234 | s := digitsToStr(v) |
| ca5fd6f | 235 | return neg ? "-" + s : s |
| ca5fd6f | 236 | |
| ca5fd6f | 237 | # Builds the decimal digits of a positive Int, most significant first, by |
| ca5fd6f | 238 | # peeling off one digit at a time from the least significant end (so the |
| ca5fd6f | 239 | # recursion emits digits in reverse call order — the leading, smallest-place |
| ca5fd6f | 240 | # digit is appended last). |
| ca5fd6f | 241 | fun digitsToStr(n: Int) -> Str = |
| ca5fd6f | 242 | if n == 0 |
| ca5fd6f | 243 | return "" |
| ca5fd6f | 244 | return digitsToStr(n / 10) + digitChar(n % 10) |
| ca5fd6f | 245 | |
| ca5fd6f | 246 | fun digitChar(d: Int) -> Str = |
| ca5fd6f | 247 | match d |
| ca5fd6f | 248 | 0 => "0" |
| ca5fd6f | 249 | 1 => "1" |
| ca5fd6f | 250 | 2 => "2" |
| ca5fd6f | 251 | 3 => "3" |
| ca5fd6f | 252 | 4 => "4" |
| ca5fd6f | 253 | 5 => "5" |
| ca5fd6f | 254 | 6 => "6" |
| ca5fd6f | 255 | 7 => "7" |
| ca5fd6f | 256 | 8 => "8" |
| ca5fd6f | 257 | 9 => "9" |
| ca5fd6f | 258 | _ => "0" |
| ca5fd6f | 259 | |
| ca5fd6f | 260 | # Renders `n`'s digits most-significant-first, always emitting exactly as many |
| ca5fd6f | 261 | # digits as `place_value` has powers of ten (leading zeros included) — used to |
| ca5fd6f | 262 | # render a fractional part at a fixed width regardless of its own leading zeros. |
| ca5fd6f | 263 | fun fixedFracDigits(n: Int, place_value: Int) -> Str = |
| ca5fd6f | 264 | if place_value == 0 |
| ca5fd6f | 265 | return "" |
| ca5fd6f | 266 | return digitChar(n / place_value % 10) + fixedFracDigits(n, place_value / 10) |
| ca5fd6f | 267 | |
| ca5fd6f | 268 | fun pow10(n: Int) -> Int = |
| ca5fd6f | 269 | if n <= 0 |
| ca5fd6f | 270 | return 1 |
| ca5fd6f | 271 | return 10 * pow10(n - 1) |
| ca5fd6f | 272 | |
| ca5fd6f | 273 | # `fixedFracDigits(n, pow10(digits - 1))`, minus however many trailing-zero |
| ca5fd6f | 274 | # digits `n` has — but always at least one digit ("0" if `n` is 0 outright). |
| ca5fd6f | 275 | fun trimmedFracDigits(n: Int, digits: Int) -> Str = |
| ca5fd6f | 276 | if n == 0 || digits <= 1 |
| ca5fd6f | 277 | return digitChar(n % 10) |
| ca5fd6f | 278 | if n % 10 == 0 |
| ca5fd6f | 279 | return trimmedFracDigits(n / 10, digits - 1) |
| ca5fd6f | 280 | return fixedFracDigits(n, pow10(digits - 1)) |
| ca5fd6f | 281 | |
| ca5fd6f | 282 | # Shared `base^y` algorithm behind `Number.pow`, extracted so both the `Int` |
| ca5fd6f | 283 | # and `Float` self cases (which only differ in how `base` was obtained) share |
| ca5fd6f | 284 | # one body. |
| ca5fd6f | 285 | fun powFloat(base: Float, y: Float) -> Float = |
| ca5fd6f | 286 | if y == 0.0f |
| ca5fd6f | 287 | return 1.0f |
| ca5fd6f | 288 | yi := Int(y) |
| ca5fd6f | 289 | if Float(yi) == y |
| ca5fd6f | 290 | neg := yi < 0 |
| ca5fd6f | 291 | n := neg ? -yi : yi |
| ca5fd6f | 292 | result := 1.0f |
| ca5fd6f | 293 | b := base |
| ca5fd6f | 294 | m := n |
| ca5fd6f | 295 | while m > 0 |
| ca5fd6f | 296 | if m % 2 == 1 |
| ca5fd6f | 297 | result = result * b |
| ca5fd6f | 298 | b = b * b |
| ca5fd6f | 299 | m = m / 2 |
| ca5fd6f | 300 | return neg ? 1.0f / result : result |
| ca5fd6f | 301 | if base < 0.0f |
| ca5fd6f | 302 | return 0.0f / 0.0f |
| ca5fd6f | 303 | return exp(y * ln(base)) |
| ca5fd6f | 304 | |
| ca5fd6f | 305 | # Parses a decimal integer, with an optional leading `+`/`-`. |
| ca5fd6f | 306 | fun parseInt(s: Str) -> Result[Int, Str] = |
| ca5fd6f | 307 | len := s.length() |
| ca5fd6f | 308 | if len == 0 |
| ca5fd6f | 309 | return Err("empty string") |
| ca5fd6f | 310 | neg := s.byteAt(0) == 45 |
| ca5fd6f | 311 | start := neg || s.byteAt(0) == 43 ? 1 : 0 |
| ca5fd6f | 312 | if start >= len |
| ca5fd6f | 313 | return Err("invalid integer: '{s}'") |
| ca5fd6f | 314 | value := 0 |
| ca5fd6f | 315 | i := start |
| ca5fd6f | 316 | while i < len |
| ca5fd6f | 317 | b := s.byteAt(i) |
| ca5fd6f | 318 | if b < 48 || b > 57 |
| ca5fd6f | 319 | return Err("invalid integer: '{s}'") |
| ca5fd6f | 320 | value = value * 10 + b - 48 |
| ca5fd6f | 321 | i = i + 1 |
| ca5fd6f | 322 | return Ok(neg ? -value : value) |
| ca5fd6f | 323 | |
| ca5fd6f | 324 | # Parses a decimal float, with an optional leading `+`/`-` and an optional |
| ca5fd6f | 325 | # `.` fractional part (no exponent notation). |
| ca5fd6f | 326 | fun parseFloat(s: Str) -> Result[Float, Str] = |
| ca5fd6f | 327 | len := s.length() |
| ca5fd6f | 328 | if len == 0 |
| ca5fd6f | 329 | return Err("empty string") |
| ca5fd6f | 330 | neg := s.byteAt(0) == 45 |
| ca5fd6f | 331 | start := neg || s.byteAt(0) == 43 ? 1 : 0 |
| ca5fd6f | 332 | if start >= len |
| ca5fd6f | 333 | return Err("invalid float: '{s}'") |
| ca5fd6f | 334 | int_part := 0.0f |
| ca5fd6f | 335 | saw_digit := False |
| ca5fd6f | 336 | i := start |
| ca5fd6f | 337 | while i < len && s.byteAt(i) != 46 |
| ca5fd6f | 338 | b := s.byteAt(i) |
| ca5fd6f | 339 | if b < 48 || b > 57 |
| ca5fd6f | 340 | return Err("invalid float: '{s}'") |
| ca5fd6f | 341 | int_part = int_part * 10.0f + Float(b - 48) |
| ca5fd6f | 342 | saw_digit = True |
| ca5fd6f | 343 | i = i + 1 |
| ca5fd6f | 344 | frac_part := 0.0f |
| ca5fd6f | 345 | frac_scale := 1.0f |
| ca5fd6f | 346 | if i < len && s.byteAt(i) == 46 |
| ca5fd6f | 347 | i = i + 1 |
| ca5fd6f | 348 | while i < len |
| ca5fd6f | 349 | b = s.byteAt(i) |
| ca5fd6f | 350 | if b < 48 || b > 57 |
| ca5fd6f | 351 | return Err("invalid float: '{s}'") |
| ca5fd6f | 352 | frac_scale = frac_scale / 10.0f |
| ca5fd6f | 353 | frac_part = frac_part + Float(b - 48) * frac_scale |
| ca5fd6f | 354 | saw_digit = True |
| ca5fd6f | 355 | i = i + 1 |
| ca5fd6f | 356 | if !saw_digit |
| ca5fd6f | 357 | return Err("invalid float: '{s}'") |
| ca5fd6f | 358 | value := int_part + frac_part |
| ca5fd6f | 359 | return Ok(neg ? -value : value) |
| ca5fd6f | 360 | |
| ca5fd6f | 361 | # A raw, host-provided pseudo-random 64-bit Int (xorshift64*, seeded from the |
| ca5fd6f | 362 | # wall clock — not cryptographically secure). Every other random-number |
| ca5fd6f | 363 | # function in std is built on top of this one host extern. |
| ca5fd6f | 364 | extern fun rawRandomInt() -> Int |
| ca5fd6f | 365 | |
| ca5fd6f | 366 | # A random Float uniformly distributed in [0.0, 1.0). |
| ca5fd6f | 367 | fun random() -> Float = |
| ca5fd6f | 368 | return Float(rawRandomInt() & MAX_VALUE) / Float(MAX_VALUE) |
| ca5fd6f | 369 | |
| ca5fd6f | 370 | # A random Int uniformly distributed in [0, n). Returns 0 for n <= 0. |
| ca5fd6f | 371 | fun randomInt(n: Int) -> Int = |
| ca5fd6f | 372 | if n <= 0 |
| ca5fd6f | 373 | return 0 |
| ca5fd6f | 374 | return { rawRandomInt() & MAX_VALUE } % n |
| ca5fd6f | 375 | |
| ca5fd6f | 376 | # Natural exponential, e^x, via range reduction (halve x until |x| <= 0.5, |
| ca5fd6f | 377 | # run the Taylor series there where it converges fast, then square the |
| ca5fd6f | 378 | # result back up the same number of halvings). |
| ca5fd6f | 379 | fun exp(x: Float) -> Float = |
| ca5fd6f | 380 | if x != x |
| ca5fd6f | 381 | return x |
| ca5fd6f | 382 | if x > 700.0f |
| ca5fd6f | 383 | return 1.0f / 0.0f |
| ca5fd6f | 384 | if x < -700.0f |
| ca5fd6f | 385 | return 0.0f |
| ca5fd6f | 386 | v := x |
| ca5fd6f | 387 | k := 0 |
| ca5fd6f | 388 | while v > 0.5f || v < -0.5f |
| ca5fd6f | 389 | v = v / 2.0f |
| ca5fd6f | 390 | k = k + 1 |
| ca5fd6f | 391 | term := 1.0f |
| ca5fd6f | 392 | sum := 1.0f |
| ca5fd6f | 393 | n := 1 |
| ca5fd6f | 394 | while n < 25 |
| ca5fd6f | 395 | term = term * v / Float(n) |
| ca5fd6f | 396 | sum = sum + term |
| ca5fd6f | 397 | n = n + 1 |
| ca5fd6f | 398 | result := sum |
| ca5fd6f | 399 | i := 0 |
| ca5fd6f | 400 | while i < k |
| ca5fd6f | 401 | result = result * result |
| ca5fd6f | 402 | i = i + 1 |
| ca5fd6f | 403 | return result |
| ca5fd6f | 404 | |
| ca5fd6f | 405 | # Natural logarithm, via range reduction to v in [1, 2) plus the |
| ca5fd6f | 406 | # fast-converging series ln(v) = 2*atanh((v-1)/(v+1)). |
| ca5fd6f | 407 | fun ln(x: Float) -> Float = |
| ca5fd6f | 408 | if x != x || x < 0.0f |
| ca5fd6f | 409 | return 0.0f / 0.0f |
| ca5fd6f | 410 | if x == 0.0f |
| ca5fd6f | 411 | return -1.0f / 0.0f |
| ca5fd6f | 412 | if x > MAX_FLOAT_VALUE |
| ca5fd6f | 413 | return x |
| ca5fd6f | 414 | v := x |
| ca5fd6f | 415 | k := 0 |
| ca5fd6f | 416 | while v >= 2.0f |
| ca5fd6f | 417 | v = v / 2.0f |
| ca5fd6f | 418 | k = k + 1 |
| ca5fd6f | 419 | while v < 1.0f |
| ca5fd6f | 420 | v = v * 2.0f |
| ca5fd6f | 421 | k = k - 1 |
| ca5fd6f | 422 | t := {v - 1.0f} / {v + 1.0f} |
| ca5fd6f | 423 | t2 := t * t |
| ca5fd6f | 424 | term := t |
| ca5fd6f | 425 | sum := t |
| ca5fd6f | 426 | n := 1 |
| ca5fd6f | 427 | while n < 30 |
| ca5fd6f | 428 | term = term * t2 |
| ca5fd6f | 429 | sum = sum + term / Float(2 * n + 1) |
| ca5fd6f | 430 | n = n + 1 |
| ca5fd6f | 431 | return Float(k) * LN2 + 2.0f * sum |
| ca5fd6f | 432 | |
| ca5fd6f | 433 | # Square root via Newton's method, iterating to a fixed point. |
| ca5fd6f | 434 | fun sqrt(x: Float) -> Float = |
| ca5fd6f | 435 | if x < 0.0f |
| ca5fd6f | 436 | return 0.0f / 0.0f |
| ca5fd6f | 437 | if x == 0.0f || x != x |
| ca5fd6f | 438 | return x |
| ca5fd6f | 439 | guess := x |
| ca5fd6f | 440 | prev := 0.0f |
| ca5fd6f | 441 | i := 0 |
| ca5fd6f | 442 | while guess != prev && i < 100 |
| ca5fd6f | 443 | prev = guess |
| ca5fd6f | 444 | guess = 0.5f * {guess + x / guess} |
| ca5fd6f | 445 | i = i + 1 |
| ca5fd6f | 446 | return guess |
| ca5fd6f | 447 | |
| ca5fd6f | 448 | # Reduces `x` into roughly `[-PI, PI]` by subtracting the nearest multiple of |
| ca5fd6f | 449 | # `TAU` — the range `sin`/`cos`'s Taylor series below actually converge |
| ca5fd6f | 450 | # quickly over. For very large `|x|` (many multiples of `TAU`), floating-point |
| ca5fd6f | 451 | # cancellation in `x - k*TAU` loses precision the same way any naive |
| ca5fd6f | 452 | # range-reduction by subtraction does; a real libm uses extended-precision |
| ca5fd6f | 453 | # constants to avoid this, which isn't attempted here. |
| ca5fd6f | 454 | fun reduceToPi(x: Float) -> Float = |
| ca5fd6f | 455 | k := Int(x / TAU + {x >= 0.0f ? 0.5f : -0.5f}) |
| ca5fd6f | 456 | return x - Float(k) * TAU |
| ca5fd6f | 457 | |
| ca5fd6f | 458 | fun sin(x: Float) -> Float = |
| ca5fd6f | 459 | if x != x || x > MAX_FLOAT_VALUE || x < -MAX_FLOAT_VALUE |
| ca5fd6f | 460 | return 0.0f / 0.0f |
| ca5fd6f | 461 | r := reduceToPi(x) |
| ca5fd6f | 462 | r2 := r * r |
| ca5fd6f | 463 | term := r |
| ca5fd6f | 464 | sum := r |
| ca5fd6f | 465 | n := 1 |
| ca5fd6f | 466 | while n < 10 |
| ca5fd6f | 467 | term = term * {-r2} / Float({2 * n} * {2 * n + 1}) |
| ca5fd6f | 468 | sum = sum + term |
| ca5fd6f | 469 | n = n + 1 |
| ca5fd6f | 470 | return sum |
| ca5fd6f | 471 | |
| ca5fd6f | 472 | fun cos(x: Float) -> Float = |
| ca5fd6f | 473 | if x != x || x > MAX_FLOAT_VALUE || x < -MAX_FLOAT_VALUE |
| ca5fd6f | 474 | return 0.0f / 0.0f |
| ca5fd6f | 475 | return sin(x + HALF_PI) |
| ca5fd6f | 476 | |
| ca5fd6f | 477 | fun tan(x: Float) -> Float = |
| ca5fd6f | 478 | return sin(x) / cos(x) |
| ca5fd6f | 479 | |
| ca5fd6f | 480 | # Arctangent, via repeated argument-halving (`atan(x) = 2*atan(x / (1 + |
| ca5fd6f | 481 | # sqrt(1+x^2)))`) until `|x| <= 0.5` (where the Taylor series below converges |
| ca5fd6f | 482 | # quickly), then doubling the result back up the same number of times. |
| ca5fd6f | 483 | fun atan(x: Float) -> Float = |
| ca5fd6f | 484 | if x != x |
| ca5fd6f | 485 | return x |
| ca5fd6f | 486 | neg := x < 0.0f |
| ca5fd6f | 487 | v := neg ? -x : x |
| ca5fd6f | 488 | k := 0 |
| ca5fd6f | 489 | while v > 0.5f && k < 8 |
| ca5fd6f | 490 | v = v / {1.0f + sqrt(1.0f + v * v)} |
| ca5fd6f | 491 | k = k + 1 |
| ca5fd6f | 492 | v2 := v * v |
| ca5fd6f | 493 | term := v |
| ca5fd6f | 494 | sum := v |
| ca5fd6f | 495 | n := 1 |
| ca5fd6f | 496 | while n < 20 |
| ca5fd6f | 497 | term = term * {-v2} |
| ca5fd6f | 498 | sum = sum + term / Float(2 * n + 1) |
| ca5fd6f | 499 | n = n + 1 |
| ca5fd6f | 500 | scale := Float(1 << k) |
| ca5fd6f | 501 | result := sum * scale |
| ca5fd6f | 502 | return neg ? -result : result |
| ca5fd6f | 503 | |
| ca5fd6f | 504 | fun asin(x: Float) -> Float = |
| ca5fd6f | 505 | if x != x || x < -1.0f || x > 1.0f |
| ca5fd6f | 506 | return 0.0f / 0.0f |
| ca5fd6f | 507 | if x == 1.0f |
| ca5fd6f | 508 | return HALF_PI |
| ca5fd6f | 509 | if x == -1.0f |
| ca5fd6f | 510 | return -HALF_PI |
| ca5fd6f | 511 | return atan(x / sqrt(1.0f - x * x)) |
| ca5fd6f | 512 | |
| ca5fd6f | 513 | fun acos(x: Float) -> Float = |
| ca5fd6f | 514 | return HALF_PI - asin(x) |
| ca5fd6f | 515 | |
| ca5fd6f | 516 | # Angle (in radians) of the point `(x, y)` from the origin, in the correct |
| ca5fd6f | 517 | # quadrant for any sign combination of `x`/`y` (unlike plain `atan(y/x)`, |
| ca5fd6f | 518 | # which can't distinguish opposite quadrants). |
| ca5fd6f | 519 | fun atan2(y: Float, x: Float) -> Float = |
| ca5fd6f | 520 | if x > 0.0f |
| ca5fd6f | 521 | return atan(y / x) |
| ca5fd6f | 522 | if x < 0.0f && y >= 0.0f |
| ca5fd6f | 523 | return atan(y / x) + PI |
| ca5fd6f | 524 | if x < 0.0f && y < 0.0f |
| ca5fd6f | 525 | return atan(y / x) - PI |
| ca5fd6f | 526 | if x == 0.0f && y > 0.0f |
| ca5fd6f | 527 | return HALF_PI |
| ca5fd6f | 528 | if x == 0.0f && y < 0.0f |
| ca5fd6f | 529 | return -HALF_PI |
| ca5fd6f | 530 | return 0.0f |
| ca5fd6f | 531 | |
| ca5fd6f | 532 | fun hypot(a: Float, b: Float) -> Float = |
| ca5fd6f | 533 | return sqrt(a * a + b * b) |
| ca5fd6f | 534 | |
| ca5fd6f | 535 | # Cube root via Newton's method (fixed iteration count, unlike `sqrt`'s |
| ca5fd6f | 536 | # converge-to-a-fixed-point loop, since the cubic update step doesn't reach an |
| ca5fd6f | 537 | # exact fixed point in float precision as reliably as the quadratic one does). |
| ca5fd6f | 538 | fun cbrt(x: Float) -> Float = |
| ca5fd6f | 539 | if x == 0.0f || x != x |
| ca5fd6f | 540 | return x |
| ca5fd6f | 541 | neg := x < 0.0f |
| ca5fd6f | 542 | v := neg ? -x : x |
| ca5fd6f | 543 | guess := v |
| ca5fd6f | 544 | i := 0 |
| ca5fd6f | 545 | while i < 60 |
| ca5fd6f | 546 | guess = {2.0f * guess + v / {guess * guess}} / 3.0f |
| ca5fd6f | 547 | i = i + 1 |
| ca5fd6f | 548 | return neg ? -guess : guess |