# Math Function Backends

Arithmetic and comparison operators work on both integers and doubles. When any operand is a double, the result is promoted to double (e.g., `(+ 1 1.5)` returns `2.5`). `+`, `-`, `*`, `/` accept two or more arguments. `mod` supports doubles in the interpreter and JVM compiler but not in the WASM compiler.

## Math function backend support

The math built-ins differ in how widely they are supported, because the WASM backend only has native instructions for a few operations:

- **`sqrt`, `isqrt`, `gcd`, `lcm`, `signum`, `expt`** are supported on all three backends (interpreter, JVM, WASM) and through the compiled `eval`. `sqrt` uses the native `f64.sqrt` instruction.
- **`exp`** is supported on all three backends. The interpreter and JVM use `Math.exp`; WASM has no native transcendental instruction, so it emits a software approximation in f64 (argument reduction by repeated squaring plus a Taylor polynomial), accurate to roughly 1e-6 relative error. The WASM result is therefore close to but not bit-identical to `Math.exp` (`(exp 0)` is exactly `1.0`). This is enough to run, e.g., a sigmoid `(/ 1.0 (+ 1.0 (exp (- 0 x))))` compiled to WASM.
- **`log` and `tanh`** are supported on all three backends the same way as `exp`: the interpreter and JVM use `Math.log`/`Math.tanh`, and WASM emits software approximations -- `log` extracts the binary exponent and evaluates a short polynomial series on the normalized mantissa (`ln(x) = e*ln(2) + ln(m)`), and `tanh` is derived from the software `exp` as `(e^(2x)-1)/(e^(2x)+1)` with the argument clamped so large inputs saturate to exactly `±1.0`. Both match the interpreter/JVM values closely but not bit-identically (`(log 1)` and `(tanh 0)` are exactly `0.0`). The IEEE edges of `log` match `Math.log` (`log(0.0)` is `-Infinity`, a negative argument is `NaN`); WASM's `(tanh -0.0)` is `0.0` where the interpreter and JVM keep `-0.0`, the same class of edge as the WASM `signum`.
- **`sin`, `cos` and `tan`** are supported on all three backends the same way: the interpreter and JVM use `Math.sin`/`Math.cos`/`Math.tan`, and WASM emits a software approximation -- Cody-Waite argument reduction (`k = nearest(x * 2/pi)`, `r = x - k*pi/2` via a two-part split of `pi/2`, the quadrant `k mod 4` selecting the sign/swap) plus Taylor polynomials for `sin(r)`/`cos(r)` on the reduced argument; `tan` is their ratio. Accuracy is ~1e-11 relative for `|x|` up to about `1e6`, after which the reduction's absolute error grows with the argument (documented like `exp`'s low-digit divergence; beyond `|x| > 2^30` a crude fold keeps the result finite but the value progressively loses significance, where the JVM's argument reduction stays exact). The zero and quadrant anchors are exact (`(sin 0)` is `0.0`, `(cos 0)` is `1.0`, `(sin (/ pi 2))` is `1.0`, `(cos pi)` is `-1.0`), and `NaN`/`±Infinity` arguments give `NaN` everywhere; WASM's `(sin -0.0)` and `(tan -0.0)` are `0.0` where the interpreter and JVM keep `-0.0`, the same class of edge as the WASM `signum` and `tanh`.
- **`asin`, `acos` and `atan`** are supported on all three backends: the interpreter and JVM use `Math.asin`/`Math.acos`/`Math.atan`, and WASM emits a software approximation -- `atan` folds the argument by its odd symmetry, by the reciprocal identity `atan(x) = pi/2 - atan(1/x)` for `|x| > 1` (which also maps `±Infinity` to `±pi/2` for free), and by two half-angle steps, then evaluates a short Taylor series (~1e-15 relative error over the whole range); `asin(x) = atan(x / sqrt((1-x)(1+x)))` and `acos(x) = 2*atan(sqrt((1-x)/(1+x)))` derive from it. `(atan 0)`, `(asin 0)` and `(acos 1)` are exactly `0.0`, `(asin ±1)` is exactly `±pi/2` and `(acos -1)` exactly `pi`; an `asin`/`acos` argument outside `[-1, 1]` is `NaN` everywhere.
- **`sinh` and `cosh`** are supported on all three backends: the interpreter and JVM use `Math.sinh`/`Math.cosh`, and WASM derives both from the software `exp` -- `e = exp(|x|)`, then `(e - 1/e)/2` with the sign restored, or `(e + 1/e)/2`. For tiny arguments the subtraction would cancel, so `sinh` switches to its odd Taylor series below `|x| = 0.25`. Accuracy follows the software `exp`: ~1e-7 relative for `|x|` up to ~20, degrading as the argument grows (like `exp`'s own divergence), and overflowing to `Infinity` slightly later than the JVM's 710.5. `(sinh 0)` is exactly `0.0` and `(cosh 0)` exactly `1.0`; `(sinh ±Infinity)` is `±Infinity` and `(cosh ±Infinity)` is `Infinity` everywhere. With these, **every transcendental built-in now works on all three backends**.
- **`expt`** keeps an exact rational result for an integer or ratio base raised to an integer exponent (with big-integer promotion on every backend -- the WASM `expt`, like all WASM integer arithmetic, stays exact at any magnitude); a negative exponent yields the reciprocal (`(expt 2 -1)` is `1/2`). A float base with an integer exponent multiplies as a float (`(expt 2.0 3)` is `8.0`), and a float or ratio exponent gives a float: the interpreter and JVM use `Math.pow`, and WASM computes a fractional power as `exp(y * log(x))` over its software `exp`/`log` -- so `(expt 10000.0 0.75)` is `1000.0` up to the low-order digits of that approximation -- with the `Math.pow` edges (`x^0.0` is `1.0`, `0^y` is `0.0` for a positive and `Infinity` for a negative `y`, a negative base to a fractional power is `NaN`). An integer-valued float exponent (`(expt 2 3.0)`) takes the exact multiplication path and is `8.0` exactly. The dispatch happens at run time on every backend, so an exponent that arrives through a variable or a function call behaves exactly like a literal one.
- **`gcd`, `lcm`, `signum`** are exact at any magnitude on every backend; **`isqrt`** still operates on the i31 integer range in the WASM backend.
- **`random`** is supported on all three backends. It returns a non-negative random number below the (positive) limit, of the same type as the limit (an integer limit yields an integer, a float limit a float). The integer and float paths are chosen from the literal shape of the argument, so use a float literal (`(random 1.0)`) when a float result is wanted. The interpreter and JVM draw from `Math.random()`; the WASM backend draws real entropy from the WASI `random_get` host function (the real `wasi_snapshot_preview1.random_get` in Preview 1 mode, `wasi:random@0.3.0` in `--component` mode), so the sequence differs each run. `random` is not available inside the compiled `eval`.
- **`logand`, `logior`, `logxor`, `lognot`, `ash`** are supported on all three backends and compute on exact integers of any magnitude (`ash` shifts left for a non-negative count, right otherwise).
