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
Every transcendental below is ONE algorithm on every backend -- fdlibm, the one java.lang.StrictMath specifies -- so (exp x), (sin x) and the rest print the same digits on the interpreter, on the JVM whatever the CPU, and on both WASM targets, with or without --simd. The interpreter and the JVM call StrictMath; WASM has no transcendental instruction, so the compiled module carries the same fdlibm as a set of runtime functions. Only --gpu's element-wise transcendentals are the device's own (GPU acceleration). What differs between the backends is how widely the other math built-ins are supported:
sqrt,isqrt,gcd,lcm,signum,exptare supported on all three backends (interpreter, JVM, WASM) and through the compiledeval.sqrtuses the nativef64.sqrtinstruction.expis supported on all three backends, fdlibm's on each:(exp 1.0)is2.7182818284590455everywhere. The IEEE edges are fdlibm's ((exp 709.78)is1.7928227943945155e308,(exp 710.0)isInfinity,(exp -1000.0)and(exp -1e30)are0.0, aNaNargument isNaN), which is what lets a-infinitymask reach a masked softmax as a weight of exactly0.0-- so a sigmoid(/ 1.0 (+ 1.0 (exp (- 0 x))))or a softmax over whole-vector kernels compiled to WASM prints what the JVM prints.logandtanhlikewise, fdlibm's on every backend.(log 1)and(tanh 0)are exactly0.0;(log 0.0)is-Infinity, while a NEGATIVE argument crosses into the complex plane everywhere --(log -1)is#C(0.0 pi)-- instead of answeringNaN;tanhis odd, so(tanh -0.0)is-0.0, and it saturates to exactly±1.0past|x| = 22.sin,cosandtanlikewise, fdlibm's on every backend, including its exact argument reduction for large arguments:(sin 1e22)is-0.8522008497671888everywhere.(sin 0)is0.0,(cos 0)is1.0,(sin (/ pi 2))is1.0,(cos pi)is-1.0;NaN/±Infinityarguments giveNaN;sinandtanare odd, so(sin -0.0)and(tan -0.0)are-0.0.asin,acosandatanlikewise, fdlibm's on every backend.(atan 0),(asin 0)and(acos 1)are exactly0.0,(asin ±1)is exactly±pi/2and(acos -1)exactlypi; anasin/acosargument outside[-1, 1]crosses into the complex plane everywhere, the waysqrtroots a negative, rather than answeringNaN.sinhandcoshlikewise, fdlibm's on every backend (computed over itsexpm1, so a tiny argument does not cancel).(sinh 0)is exactly0.0and(cosh 0)exactly1.0;(sinh ±Infinity)is±Infinityand(cosh ±Infinity)isInfinity, and both overflow toInfinityat the same edge everywhere. With these, every transcendental built-in works on all three backends, with one set of digits.exptkeeps an exact rational result for an integer or ratio base raised to an integer exponent (with big-integer promotion on every backend -- the WASMexpt, like all WASM integer arithmetic, stays exact at any magnitude); a negative exponent yields the reciprocal ((expt 2 -1)is1/2). A float anywhere, or a ratio exponent, makes the answer fdlibm'spowon every backend (StrictMath.powon the interpreter and the JVM;(expt 2.0 3)is8.0,(expt 10000.0 0.75)is1000.0,(expt 1.1 10)is2.5937424601000023everywhere), with pow's edges (x^0.0is1.0,0^yis0.0for a positive andInfinityfor a negativey). A negative base to a non-integer power crosses into the complex plane on every backend rather than answeringNaN: the modulus|base|^powerturned throughpower * piradians, an exactly known phase that the complexexp(w * log z)form would have to recover from a logarithm. An integer-valued float exponent ((expt 2 3.0)) is8.0exactly, pow's own answer. 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.asinh,acosh,atanhandcisare supported on all three backends.java.lang.Mathhas no inverse hyperbolic, so all three are one hand-rolled formula each over fdlibm'slog1p,logandhypot, evaluated the same way by the interpreter, the JVM and the WASM backends, so the bits agree everywhere. A real argument outside the real domain crosses into the complex plane the waysqrtroots a negative --(acosh 0)and(atanh 2)answer complex -- so the JVM and WASM call sites carry the plane as a runtime branch likesqrtdoes.cisalways answers a complex:(cis x)is(cos x, sin x), and a complex argument decays bye^{-im}.(asinh 0),(acosh 1)and(atanh 0)are exactly0.0and(cis 0)exactly#C(1.0 0.0)on every backend.gcd,lcm,signumare exact at any magnitude on every backend;isqrtstill operates on the i31 integer range in the WASM backend.floor,ceiling,round,truncateare exact at any magnitude on every backend, in both of the values they return. A finite float is already a mathematical integer above 2^52, and a division of two floats has exactly one mathematical quotient, so the quotient is that integer -- promoted to a big integer when it exceeds the fixnum range, like every other numeric operator, never clamped to it.(floor 1d300)is the exact 301-digit value of that double, and(truncate 1d300 7.0)its exact quotient with a remainder of1.0. The second value satisfiesquotient*divisor + remainder = numberand is the same quantityremanswers fortruncateandmodforfloor. This is not only about huge values: the double0.1is a shade above a tenth, so(floor 1.0 0.1)is9with a remainder of0.09999999999999995-- the quotient of the two values as they actually are, and the same remaindermodgives -- where rounding1.0/0.1to10.0first would answer10. The--no-gcWASM lowering is the exception to the exactness: its integers are 64-bit by design, so a quotient past that range has no representation there.randomis 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.ThreadLocalRandomon 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 realwasi_snapshot_preview1.random_getin Preview 1 mode,wasi:random@0.3.0in--componentmode), so the sequence differs each run.randomis not available inside the compiledeval.logand,logior,logxor,lognot,ashare supported on all three backends and compute on exact integers of any magnitude (ashshifts left for a non-negative count, right otherwise).