(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 in the interpreter and JVM); a negative exponent yields the reciprocal ((expt 2 -1) is 1/2) and a float base/exponent uses Math.pow and returns a float. The WASM expt, like all WASM integer arithmetic, uses 31-bit values with no overflow promotion.
  • isqrt, gcd, lcm, signum operate on the i31 integer range in the WASM backend (no big-integer promotion); the interpreter and JVM promote to big integers as needed.
  • 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. The interpreter and JVM compute on exact BigInteger values (ash shifts left for a non-negative count, right otherwise); the WASM backend uses the i31 integer range, so a result that overflows 31 bits is truncated.