数学関数のバックエンド
算術演算子と比較演算子は整数と倍精度浮動小数点数の両方で動作します。いずれかのオペランドが倍精度浮動小数点数の場合、結果は倍精度に昇格します(例: (+ 1 1.5) は 2.5 を返します)。+、-、*、/ は 2 つ以上の引数を受け付けます。mod はインタプリタと JVM コンパイラでは倍精度をサポートしますが、WASM コンパイラではサポートしません。
数学関数のバックエンドサポート
WASM バックエンドは少数の演算に対してしかネイティブ命令を持たないため、数学組み込み関数はサポートの範囲が異なります。
sqrt、isqrt、gcd、lcm、signum、exptは 3 つのバックエンドすべて(インタプリタ、JVM、WASM)とコンパイルされたevalでサポートされます。sqrtはネイティブのf64.sqrt命令を使用します。expは 3 つのバックエンドすべてでサポートされます。インタプリタと JVM はMath.expを使用します。WASM には超越関数のネイティブ命令がないため、f64 でのソフトウェア近似(繰り返し二乗による引数縮約とテイラー多項式)を出力し、相対誤差はおおよそ 1e-6 の精度です。したがって WASM の結果はMath.expに近いものの、ビット単位で同一ではありません((exp 0)は正確に1.0です)。これは、例えば WASM にコンパイルしたシグモイド(/ 1.0 (+ 1.0 (exp (- 0 x))))を実行するのに十分です。logとtanhはexpと同じ方法で 3 つのバックエンドすべてでサポートされます。インタプリタと JVM はMath.log/Math.tanhを使い、WASM はソフトウェア近似を出力します —logは二進指数部を抽出して正規化した仮数に短い多項式級数を評価し(ln(x) = e*ln(2) + ln(m))、tanhはソフトウェアexpから(e^(2x)-1)/(e^(2x)+1)として導出します(引数をクランプするため、大きな入力は正確に±1.0に飽和します)。どちらもインタプリタ/JVM の値に近いもののビット単位では同一ではありません((log 1)と(tanh 0)は正確に0.0です)。logの IEEE の端値はMath.logと一致します(log(0.0)は-Infinity、負の引数はNaN)。WASM の(tanh -0.0)は0.0で、インタプリタと JVM は-0.0を保ちます(WASM のsignumと同じ種類の端値です)。sin、cos、tanも同じ方法で 3 つのバックエンドすべてでサポートされます。インタプリタと JVM はMath.sin/Math.cos/Math.tanを使い、WASM はソフトウェア近似を出力します — Cody-Waite 引数縮約(k = nearest(x * 2/pi)、pi/2の 2 分割定数によるr = x - k*pi/2、象限k mod 4で符号と入れ替えを選択)と、縮約後の引数に対するsin(r)/cos(r)の Taylor 多項式で、tanはその比です。精度は|x|がおよそ1e6までで相対 ~1e-11、それを超えると縮約の絶対誤差が引数とともに増えます(expの下位桁の発散と同様に文書化された挙動です。|x| > 2^30を超えると粗い折り畳みで結果は有限に保たれますが、JVM の引数縮約が正確なままなのに対し値は徐々に有効数字を失います)。ゼロと象限のアンカーは正確で((sin 0)は0.0、(cos 0)は1.0、(sin (/ pi 2))は1.0、(cos pi)は-1.0)、NaN/±Infinityの引数はどこでもNaNになります。WASM の(sin -0.0)と(tan -0.0)は0.0で、インタプリタと JVM は-0.0を保ちます(WASM のsignumやtanhと同じ種類の端値です)。asin・acos・atanは 3 つのバックエンドすべてでサポートされます。インタプリタと JVM はMath.asin/Math.acos/Math.atanを使い、WASM はソフトウェア近似を出力します。atanは奇対称による折り返し、|x| > 1に対する逆数恒等式atan(x) = pi/2 - atan(1/x)(これにより±Infinityは自然に±pi/2に写ります)、半角式 2 回で引数を縮約してから短い Taylor 級数を評価します(全域で相対誤差 ~1e-15)。asin(x) = atan(x / sqrt((1-x)(1+x)))とacos(x) = 2*atan(sqrt((1-x)/(1+x)))はそこから導出されます。(atan 0)・(asin 0)・(acos 1)は正確に0.0、(asin ±1)は正確に±pi/2、(acos -1)は正確にpiです。asin/acosの引数が[-1, 1]の外なら、どのバックエンドでもNaNです。sinh・coshは 3 つのバックエンドすべてでサポートされます。インタプリタと JVM はMath.sinh/Math.coshを使い、WASM はソフトウェアexpから導出します —e = exp(|x|)を求めて(e - 1/e)/2(符号を復元)または(e + 1/e)/2です。微小な引数では減算が桁落ちするため、sinhは|x| = 0.25未満で奇 Taylor 級数に切り替わります。精度はソフトウェアexpに従い、|x|が ~20 までで相対誤差 ~1e-7、引数が大きくなるにつれ低下し(exp自体の乖離と同様)、Infinityへのオーバーフローは JVM の 710.5 よりわずかに遅れます。(sinh 0)は正確に0.0、(cosh 0)は正確に1.0で、(sinh ±Infinity)は±Infinity、(cosh ±Infinity)はInfinityです。これにより、すべての超越関数の組込みが 3 バックエンド全部で動くようになりました。exptは、整数または比の底を整数の指数で累乗する場合、厳密な有理数結果を保ちます(インタプリタと JVM では多倍長整数への昇格を伴います)。負の指数は逆数を返し((expt 2 -1)は1/2)、浮動小数点の底/指数の場合はMath.powを使って浮動小数点数を返します。WASM のexptは、すべての WASM 整数算術と同様に 31 ビット値を使い、オーバーフロー昇格はありません。isqrt、gcd、lcm、signumは WASM バックエンドでは i31 整数範囲で演算します(多倍長整数への昇格なし)。インタプリタと JVM は必要に応じて多倍長整数に昇格します。randomは 3 つのバックエンドすべてでサポートされます。(正の)上限未満の非負の乱数を、上限と同じ型で返します(整数の上限は整数を、浮動小数点の上限は浮動小数点数を返します)。整数パスと浮動小数点パスは引数のリテラルの形から選ばれるため、浮動小数点の結果が欲しい場合は浮動小数点リテラル((random 1.0))を使ってください。インタプリタと JVM はMath.random()から取得します。WASM バックエンドは WASI のrandom_getホスト関数から実際のエントロピーを取得するため(Preview 1 モードでは実際のwasi_snapshot_preview1.random_get、--componentモードではwasi:random@0.3.0)、シーケンスは実行ごとに異なります。randomはコンパイルされたevalの中では利用できません。logand、logior、logxor、lognot、ashは 3 つのバックエンドすべてでサポートされます。インタプリタと JVM は厳密なBigInteger値で計算します(ashはシフト数が非負なら左、そうでなければ右にシフトします)。WASM バックエンドは i31 整数範囲を使うため、31 ビットをオーバーフローする結果は切り詰められます。