(rontolisp) docs

数学関数のバックエンド

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

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

以下の超越関数はどのバックエンドでも同じ 1 つのアルゴリズム -- java.lang.StrictMath が規定する fdlibm -- で計算されるため、(exp x) や (sin x) などはインタプリタでも、CPU を問わず JVM でも、--simd の有無にかかわらず両方の WASM ターゲットでも同じ桁を印字します。インタプリタと JVM は StrictMath を呼び、WASM には超越関数のネイティブ命令がないため、コンパイルされたモジュールが同じ fdlibm をランタイム関数の集合として持ち運びます。--gpu の要素ごとの超越関数だけはデバイス自身のものです(GPU アクセラレーション)。バックエンド間で異なるのは、その他の数学組み込み関数がどこまでサポートされるかです。

  • sqrt、isqrt、gcd、lcm、signum、expt は 3 つのバックエンドすべて(インタプリタ、JVM、WASM)とコンパイルされた eval でサポートされます。sqrt はネイティブの f64.sqrt 命令を使用します。
  • exp は 3 つのバックエンドすべてでサポートされ、どこでも fdlibm の値です: (exp 1.0) はどこでも 2.7182818284590455 です。IEEE の端値も fdlibm のものです((exp 709.78) は 1.7928227943945155e308、(exp 710.0) は Infinity、(exp -1000.0) と (exp -1e30) は 0.0、引数 NaN は NaN)。これにより -infinity のマスクはマスク付き softmax に重みちょうど 0.0 として届くので、WASM にコンパイルしたシグモイド (/ 1.0 (+ 1.0 (exp (- 0 x)))) やベクトルカーネル上の softmax は JVM と同じものを印字します。
  • log と tanh も同様に、どのバックエンドでも fdlibm の値です。(log 1) と (tanh 0) は正確に 0.0、(log 0.0) は -Infinity で、負の引数はどのバックエンドでも NaN ではなく複素平面へ跨ぎます -- (log -1) は #C(0.0 pi) です。tanh は奇関数なので (tanh -0.0) は -0.0 で、|x| = 22 を超えると正確に ±1.0 に飽和します。
  • sin、cos、tan も同様に、大きな引数に対する正確な引数縮約も含めて、どのバックエンドでも fdlibm の値です: (sin 1e22) はどこでも -0.8522008497671888 です。(sin 0) は 0.0、(cos 0) は 1.0、(sin (/ pi 2)) は 1.0、(cos pi) は -1.0 で、NaN/±Infinity の引数は NaN になります。sin と tan は奇関数なので、(sin -0.0) と (tan -0.0) は -0.0 です。
  • asin・acos・atan も同様に、どのバックエンドでも fdlibm の値です。(atan 0)・(asin 0)・(acos 1) は正確に 0.0、(asin ±1) は正確に ±pi/2、(acos -1) は正確に pi です。asin/acos の引数が [-1, 1] の外にあるときは、sqrt が負数を複素平面に送るのと同じように、どのバックエンドでも複素平面へ跨ぎます (NaN ではありません)。
  • sinh・cosh も同様に、どのバックエンドでも fdlibm の値です(その expm1 上で計算されるため、微小な引数でも桁落ちしません)。(sinh 0) は正確に 0.0、(cosh 0) は正確に 1.0 で、(sinh ±Infinity) は ±Infinity、(cosh ±Infinity) は Infinity、どちらもどこでも同じ端で Infinity にオーバーフローします。これにより、すべての超越関数の組込みが 3 バックエンド全部で、1 組の桁で動きます。
  • expt は、整数または比の底を整数の指数で累乗する場合、厳密な有理数結果を保ちます(どのバックエンドでも多倍長整数への昇格を伴い、WASM の expt はすべての WASM 整数算術と同様に任意の大きさで正確です)。負の指数は逆数を返します((expt 2 -1) は 1/2)。どこかに浮動小数点数があるか、指数が比であれば、答えはどのバックエンドでも fdlibm の pow です(インタプリタと JVM では StrictMath.pow。(expt 2.0 3) は 8.0、(expt 10000.0 0.75) は 1000.0、(expt 1.1 10) はどこでも 2.5937424601000023)。端の振る舞いは pow のものです(x^0.0 は 1.0、0^y は正の y で 0.0、負の y で Infinity)。負の底を整数でない指数で累乗すると、どのバックエンドでも NaN ではなく複素平面へ跨ぎます。答えは絶対値 |base|^power を power * pi ラジアン回転させたもので、この位相は正確に分かっているため、複素数の exp(w * log z) 形式のように対数から復元する必要がありません。整数値の浮動小数点の指数((expt 2 3.0))は pow 自身の答えとして正確に 8.0 になります。振り分けはどのバックエンドでも実行時に行われるため、変数や関数呼び出しを経て届いた指数もリテラルとまったく同じように振る舞います。
  • asinh・acosh・atanh・cis は 3 つのバックエンドすべてでサポートされます。java.lang.Math に逆双曲線関数がないため、3 つとも fdlibm の log1p・log・hypot 上の手組みの式 1 つずつで、インタプリタ・JVM・WASM バックエンドが同じ式を同じ順で評価するため、ビットはどこでも一致します。定義域外の実数引数は sqrt が負の数を複素平面に送るのと同じく複素平面へ跨ぎます -- (acosh 0) と (atanh 2) は複素数で答えます -- そのため JVM と WASM の呼出箇所は sqrt と同じく実行時分岐で複素平面を担います。cis は常に複素数を返します: (cis x) は (cos x, sin x) で、複素数の引数では e^{-im} で減衰します。(asinh 0)・(acosh 1)・(atanh 0) は正確に 0.0、(cis 0) は正確に #C(1.0 0.0) で、これはどのバックエンドでも同じです。
  • gcd、lcm、signum はどのバックエンドでも任意の大きさで正確です。isqrt は WASM バックエンドでは引き続き i31 整数範囲で演算します。
  • floor、ceiling、round、truncate は、返す 2 つの値のどちらもどのバックエンドでも任意の大きさで正確です。有限の浮動小数点数は 2^52 を超えるとそれ自体が数学的な整数であり、2 つの浮動小数点数の除算の数学的な商はただ 1 つなので、商はその整数になります — 他のすべての数値演算子と同じように、fixnum の範囲を超えれば多倍長整数へ昇格し、そこで頭打ちにはなりません。(floor 1d300) はその double の正確な 301 桁の値であり、(truncate 1d300 7.0) は正確な商と剰余 1.0 を返します。第 2 の値は quotient*divisor + remainder = number を満たし、truncate では rem、floor では mod が返すものと同じ量です。これは巨大な値に限った話ではありません。double の 0.1 は 10 分の 1 よりわずかに大きいため、(floor 1.0 0.1) は 9 と剰余 0.09999999999999995 になります — 実際の値どうしの商であり、mod が返すのと同じ剰余です。先に 1.0/0.1 を 10.0 に丸めてしまうと 10 になります。正確さの例外は --no-gc WASM ロワリングで、そこでは整数が設計上 64 ビットのため、その範囲を超える商には表現がありません。
  • 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 はシフト数が非負なら左、そうでなければ右にシフトします)。