(rontolisp) docs

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. Every backend draws from a generator inside the program — java.util.concurrent.ThreadLocalRandom on the interpreter and JVM, a built-in generator on WASM — which is seeded once per run from the host's entropy where there is a host (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).