数学関数のバックエンド
算術演算子と比較演算子は整数と倍精度浮動小数点数の両方で動作します。いずれかのオペランドが倍精度浮動小数点数の場合、結果は倍精度に昇格します(例: (+ 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は、整数または比の底を整数の指数で累乗する場合、厳密な有理数結果を保ちます(どのバックエンドでも多倍長整数への昇格を伴い、WASM のexptはすべての WASM 整数算術と同様に任意の大きさで正確です)。負の指数は逆数を返します((expt 2 -1)は1/2)。浮動小数点の底と整数の指数は浮動小数点として乗算され((expt 2.0 3)は8.0)、浮動小数点または比の指数は浮動小数点数を返します。インタプリタと JVM はMath.powを使い、WASM は分数の指数をソフトウェアexp/logによるexp(y * log(x))として計算するため --(expt 10000.0 0.75)はその近似の下位桁を除いて1000.0です --Math.powと同じ端の振る舞いを持ちます(x^0.0は1.0、0^yは正のyで0.0、負のyでInfinity、負の底の分数乗はNaN)。整数値の浮動小数点の指数((expt 2 3.0))は厳密な乗算の経路を通り、正確に8.0になります。振り分けはどのバックエンドでも実行時に行われるため、変数や関数呼び出しを経て届いた指数もリテラルとまったく同じように振る舞います。gcd、lcm、signumはどのバックエンドでも任意の大きさで正確です。isqrtは WASM バックエンドでは引き続き i31 整数範囲で演算します。randomは 3 つのバックエンドすべてでサポートされます。(正の)上限未満の非負の乱数を、上限と同じ型で返します(整数の上限は整数を、浮動小数点の上限は浮動小数点数を返します)。整数パスと浮動小数点パスは引数のリテラルの形から選ばれるため、浮動小数点の結果が欲しい場合は浮動小数点リテラル((random 1.0))を使ってください。どのバックエンドもプログラム内部の生成器から draw します — インタプリタと JVM はjava.util.concurrent.ThreadLocalRandom、WASM は組み込みの生成器です。ホストがある場合、その生成器は実行ごとに一度だけホストのエントロピー(Preview 1 モードでは実際のwasi_snapshot_preview1.random_get、--componentモードではwasi:random@0.3.0)でシードされるため、シーケンスは実行ごとに異なります。randomはコンパイルされたevalの中では利用できません。logand、logior、logxor、lognot、ashは 3 つのバックエンドすべてでサポートされ、任意の大きさの厳密な整数で計算します(ashはシフト数が非負なら左、そうでなければ右にシフトします)。