(rontolisp) docs

数学関数のバックエンド

算術演算子と比較演算子は整数と倍精度浮動小数点数の両方で動作します。いずれかのオペランドが倍精度浮動小数点数の場合、結果は倍精度に昇格します(例: (+ 1 1.5)2.5 を返します)。+-*/ は 2 つ以上の引数を受け付けます。mod はインタプリタと JVM コンパイラでは倍精度をサポートしますが、WASM コンパイラではサポートしません。

数学関数のバックエンドサポート

WASM バックエンドは少数の演算に対してしかネイティブ命令を持たないため、数学組み込み関数はサポートの範囲が異なります。

  • sqrtisqrtgcdlcmsignumexpt は 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)))) を実行するのに十分です。
  • logtanhexp と同じ方法で 3 つのバックエンドすべてでサポートされます。インタプリタと JVM は Math.logMath.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 と同じ種類の端値です)。
  • sincostan も同じ方法で 3 つのバックエンドすべてでサポートされます。インタプリタと JVM は Math.sinMath.cosMath.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 の signumtanh と同じ種類の端値です)。
  • asinacosatan は 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 です。
  • sinhcosh は 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 ビット値を使い、オーバーフロー昇格はありません。
  • isqrtgcdlcmsignum は 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 の中では利用できません。
  • logandlogiorlogxorlognotash は 3 つのバックエンドすべてでサポートされます。インタプリタと JVM は厳密な BigInteger 値で計算します(ash はシフト数が非負なら左、そうでなければ右にシフトします)。WASM バックエンドは i31 整数範囲を使うため、31 ビットをオーバーフローする結果は切り詰められます。