(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 は、整数または比の底を整数の指数で累乗する場合、厳密な有理数結果を保ちます(どのバックエンドでも多倍長整数への昇格を伴い、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.01.00^y は正の y0.0、負の yInfinity、負の底の分数乗は NaN)。整数値の浮動小数点の指数((expt 2 3.0))は厳密な乗算の経路を通り、正確に 8.0 になります。振り分けはどのバックエンドでも実行時に行われるため、変数や関数呼び出しを経て届いた指数もリテラルとまったく同じように振る舞います。
  • gcdlcmsignum はどのバックエンドでも任意の大きさで正確です。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 の中では利用できません。
  • logandlogiorlogxorlognotash は 3 つのバックエンドすべてでサポートされ、任意の大きさの厳密な整数で計算します(ash はシフト数が非負なら左、そうでなければ右にシフトします)。