数学関数のバックエンド
算術演算子と比較演算子は整数と倍精度浮動小数点数の両方で動作します。いずれかのオペランドが倍精度浮動小数点数の場合、結果は倍精度に昇格します(例: (+ 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-gcWASM ロワリングで、そこでは整数が設計上 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はシフト数が非負なら左、そうでなければ右にシフトします)。