Skip to content

Instantly share code, notes, and snippets.

@tompng
Last active July 7, 2026 03:34
Show Gist options
  • Select an option

  • Save tompng/ffd4f545f1a3467357ddcd37d59db81d to your computer and use it in GitHub Desktop.

Select an option

Save tompng/ffd4f545f1a3467357ddcd37d59db81d to your computer and use it in GitHub Desktop.
Discussion memo of https://github.com/ruby/bigdecimal/pull/524 by Fable 5

BigMath.gamma の計算法: 旧実装・新実装・既存手法・その先

任意精度のガンマ関数 BigMath.gamma(x, prec) / BigMath.lgamma(x, prec) の計算法についてのまとめ。 このドキュメントは単体で読めるように書いてある(コードや diff を参照しなくても完結する)。

記法: p = 要求精度(10進桁数)。乗算コストは断りのない限り準線形 M(n) = O(n log n) モデルで数え、 大きい数×小さい数の積は M(m, n) = (n/m)·M(m) = O(n·log m)(小さい方のサイズでブロック化)と数える。

1. 問題の構造

ガンマ関数の任意精度計算の難しさは、x の種類によってコスト構造が全く違うこと:

x の種類 効く手法
整数 gamma(100000) 階乗そのもの(積)
桁数の少ない実数 gamma(1.25) 級数/補間 + binary splitting
フル桁の実数 gamma(√2) を10万桁 級数/補間の全精度演算 ~p 回が不可避
巨大な引数 gamma(10¹⁷), lgamma(1e400) 漸近展開 or 何らかの引数削減

x < 0.5 は反射公式 Γ(z)Γ(1−z) = π/sin(πz) で正側に移せるので、以下 x ≥ 0.5 を考える。

2. 旧実装 (〜v4.1.x): Spouge の近似式

$$\Gamma(z+1) = (z+a)^{z+1/2} e^{-(z+a)} \left[\sqrt{2\pi} + \sum_{k=1}^{a-1} \frac{c_k}{z+k} + \varepsilon\right],\quad c_k = \frac{(-1)^{k-1}}{(k-1)!}(a-k)^{k-1/2}e^{a-k}$$

精度 p 桁には a ≈ p/log₁₀(2π) ≈ 1.25p 項が必要。

  • 全精度の除算(と項ごとの sqrt)が ~1.25p 回 → O(p²·log p)
  • 係数 c_k に平方根 (a−k)^(k−1/2) が入るため、項比が有理数にならず binary splitting が効かない。 桁数の少ない x でも p² から逃げられない
  • コストが x に対して平坦: gamma(10¹⁷) でも安くならない(旧実装で 71s @1万桁)
  • 利点: x > 0 全域で収束する単一の式、実装が短い

実測(旧実装): gamma(1.25, 10⁴) = 49s、gamma(1/3 フル桁, 10⁴) = 56s、gamma(10¹⁷, 10⁴) = 71s。

なお Spouge の式は「Γ をスターリング風にスケールした有理型関数の、留数による部分分数展開の打ち切り」 であり、後述の新実装と同じ「スケールして補間する」哲学の一種と解釈できる (スケーリング関数を (x+a−1)!·e^{−x−a}/(x+a)^{x+1/2} に取り、無限遠での収束条件を課すと Spouge が再現される)。

3. 標準的な既存手法(他ライブラリ)

3.1 Stirling 漸近展開 + 引数シフト + Bernoulli 数 (mpmath / MPFR / Arb)

$$\log\Gamma(x) \sim \left(x-\tfrac12\right)\log x - x + \tfrac12\log 2\pi + \sum_{k\ge1} \frac{B_{2k}}{2k(2k-1)x^{2k-1}}$$

x が小さければ rising factorial x(x+1)…(x+n−1) で x ≈ 0.4p までシフトしてから適用。 全精度演算は級数 ~0.4p 項 + シフト ~0.4p 回で、演算回数は最少級。業界標準。

弱点は Bernoulli 数の生成コスト:

  • 素朴な漸化式は N 個で O(N²) 回の p 桁加算。full 桁の x では N ≈ 0.2p 必要なので O(p³) 級になり全く使えない
  • 高速生成(ζ 経由、冪級数反転 + Kronecker 代入)はそれ自体がライブラリ級の仕事
  • 実用上はキャッシュ前提: mpmath は gamma(1.25) 2万桁が初回 226s、キャッシュ後 3.7s

さらに原理的な限界がある(§6.1「Bernoulli データ床」): full 桁の x では、 必要な B₂ₖ 列の総データ量が Θ(p²/log p) ビットあり、係数列を実体化する限り読むだけで p²/log p

3.2 不完全ガンマ級数 (Brent 1978 / D.M. Smith, TOMS Algorithm 814)

$$\Gamma(a) = \gamma(a,r) + \Gamma(a,r),\qquad \gamma(a,r) = r^a e^{-r}\sum_{n\ge0}\frac{r^n}{a(a+1)\cdots(a+n)}$$

r ≈ p·ln10 に取ると裾 Γ(a,r) ≈ r^{a−1}e^{−r} < 10^{−p}。級数は全項が正(桁落ちが存在しない)で、 必要項数は (1+√2)·p·ln10 ≈ 5.6p 項。項比 r/(a+n) は有理数なので binary splitting / バッチ化が効く。

  • 利点: 誤差解析が厳密かつ自明(裾の明示上界 + 正項級数の打ち切り)。Bernoulli 不要
  • 弱点: x ≫ p では項数が √(x·p) に発散するため、巨大 x には別の手法が結局必要。 また通常帯域でも新実装より全精度演算が 1.5〜2 倍多い

3.3 整数 x の厳密階乗 (Arb など)

整数 x はコスト ∝ x·polylog で n! を厳密計算して丸めるのが、x が小さいうちは最速 (GMP の積木は極めて速い)。x が大きくなると Stirling に切り替える。 Arb は dps=10⁵ でこの切り替え境界が x ≈ 2×10⁷ にあり、境界直上 + Bernoulli キャッシュが 冷えている初回はどちらの戦略も最悪になる「谷」が実測で観測できる(§7)。

3.4 有理数の特殊値

Γ(1/3) のような固定有理数は hypergeometric 級数の binary splitting で準線形時間で計算できる (CLN の手法)。一般の x には適用できない。

4. 新実装: b^x/x! のラグランジュ補間

4.1 核心のアイデア

Γ(x) を直接等間隔ノードで多項式補間することは、極と Runge 現象のため破綻する。代わりに

$$f(x) = \frac{b^x}{x!}$$

を補間する。f は整関数(極なし)で、x = b を中心とするほぼ対称なベル曲線 (スターリング近似より f(b+y) ≈ f(b)·e^{−y²/2b})になり、等間隔整数ノード x_i = b−l, …, b+l での局所補間が高精度に効く。x! = b^x / f(x) で復元する。

スケーリングに b^x を選んだことが全てを決める。f(n+1)/f(n) = b/(n+1) が 「小さい有理数」になるため:

  1. barycentric 形式の係数列 w_i·f(x_i) が有理数の漸化式で生成できる (比は −b(2l−j+1)/(j·(b−l+j)))→ binary splitting が効く(Spouge には √ があって効かない)
  2. 同じ理由で、full 桁 x にはバッチ多項式評価(BSGS)が効く
  3. ノード値が階乗そのものなので、階乗の高速計算と相互再帰できる(→ 倍数公式の実用化)

candidates としては x!/b^x, x!/x^x, x!/x^b なども考えられるが、 比が有理数になり、かつ補間点数が最少になるのは b^x/x! である。

4.2 補間の形と誤差

barycentric(第1形式):

$$f(x) \approx \omega(x)\sum_{i}\frac{w_i f(x_i)}{x-x_i},\qquad \omega(x)=\prod_i (x-x_i)$$

誤差はガウス曲線モデルから E ~ (l/2eb)^l と見積もられ、E ≤ 10^{−p} より l ≈ p / log₁₀(2eb/l)(不動点反復2回で解く。初期値 l = p は根の上側から単調収束するので安全側)。

x < 2p のときは x を 2p 以上へシフトしてから補間する(x! = x(x−1)…·(x−shift)! で戻す)。 ノードの正値性の理論的下限は b ≈ 1.36p だが、b = 2p が経験的に総コスト最適。 b = 2p のとき l ≈ p、シフト積 ≈ 2p 項で、全体は「~4p 個の1次因子の処理」に帰着する。

4.3 評価戦略(x の桁数で分岐)

BSM(短桁 x、準線形): x を 10^(小数部桁数) 倍して全体を整数化し、 [部分和, 乗率, 分母] の3つ組を整数の binary splitting で畳む。 中間整数は prec·log₂10 + 64 bit で下位ビットを捨てる(bit-drop)。 捨てた 2^s は後述の exp2 チャネルで無料回収。O(p·log³p)

BSGS(フル桁 x): log₂(p) 個ずつのバッチで Π(x−k) を整数係数多項式として展開し、 事前計算した x の冪で評価する。合成除算(Ruffini)で Σc_k/(x−k) のバッチも同じ多項式評価に落とす。 バッチを √l でなく log p に取るのは係数サイズ (b+l)^batch の爆発を防ぐため。 O(p²·log log p)(unbalanced 積をブロック化して数えるモデル。実装は小さい係数を schoolbook で掛けるので文字通りには log 一つ悪いが、word パッキングの定数 ~1/word² が 効いて実測は p²。クロスオーバーは 10 の数十〜百乗桁の彼方)。

分岐条件は「BSM の全精度演算換算 l·n_sig/p 個」対「BSGS の l·p ビット」の比較で n_sig·log₂(p) > p。

4.4 Factorial doubling(倍数公式の実用化)

補間の再構成には (b−l)! が要る。x が巨大だとこれが巨大整数の階乗になる。そこで Legendre 倍数公式

$$n! = \left(\frac{n}{2}\right)!\left(\frac{n-1}{2}\right)!\cdot\frac{2^n}{\sqrt\pi}$$

で半分に落とす。片方は整数の階乗(再帰)、もう片方は半整数の階乗で、 これがまさに上の補間の BSM(x = k+0.5、シフト不要、指数が半整数なので b 冪は整数冪×√b)で 準線形時間で求まる。

通常この公式は無意味である — (n/2±0.5)! を求めるにはガンマの近似計算が要り、 それなら n! を直接近似した方が速い(Stirling は x が大きいほど収束が良い)からだ。 半整数階乗が準線形で出るこの方法で初めて実用になる。私の知る限り、 倍数公式を階乗計算の主経路に使っている既存ライブラリはない。

再帰の帳簿は n! = base·small!·(large!)²·2^n/√π の形(large! が再帰で2乗される)を [base_power_part, factorial_power_part, exp2, exp_sqrtpi] として持ち、 2^i 乗は最後にまとめて計算する。gamma(10¹⁷, 10⁴) は log₂(10¹⁷/4p) ≈ 41 段で 1.0s。

4.5 lgamma の遠方だけ Stirling

lgamma の定義域は指数が無制限(x = 1e400 など)で、doubling のコストは ∝ log x で増える一方、 Stirling は必要項数 N ≈ p·ln10/(2·ln 2πx) が x が大きいほど減る。必ず交差するので、 遠方の Stirling は不可避。切り替え条件を log₁₀x > √p/6 に置くと N < 3√p が保証され、 素朴な O(N²) の Bernoulli 漸化式が漸近的に無害になる(= 高速 Bernoulli 生成は永遠に不要)。

gamma 側は結果が overflow する x ≲ 4×10¹⁷ で定義域が切れるため doubling だけで足り、 gamma は全定義域で近似誤差モデルが1つになる。

4.6 実装上の要点(保守のための invariant)

  • bit-drop の予算: 保持ビット = (10·prec + 192)/3 = prec·log₂10 強 + 64bit。 マージ1段 ~1bit の損失 × 木の深さ log₂(2l) を 64bit が吸収
  • exp2 チャネル: bit-drop で捨てた 2^s は、補間の返り値 [base, large!, small!, exp2] の exp2 に負号で乗せ、doubling の既存の 2^exp2 会計に合流させる(冪計算を払わない)。 階乗パラメータ側の fact range 積だけは厳密に計算する(捨てた 2^s が 2^index 乗されて 指数レンジを壊すため)
  • BSGS の batch_prod 相殺: x がノード直近のとき batch_prod は激しく桁落ちするが、 同じ計算値が sum の分母と prod の因子の両方に使われるため最終結果では厳密に相殺する。 batch_prod を別々に計算するリファクタはノード直近入力だけを壊す
  • factorial_power_part の単調性: 深い再帰ほど b が小さく l が大きいので非減少。 増分更新はこれに依存
  • 検証は (i) 恒等式(漸化式・反射・倍数公式・lgamma=log∘gamma)のクロスチェック、 (ii) mpmath との高精度突き合わせ、(iii) 各経路の prec vs prec+300 の自己整合、で行う

5. この方法のどこが嬉しいのか

  1. Bernoulli 数が本質パスから消える。ステートレスでキャッシュ不要。 ワンショットの呼び出しで、キャッシュ済み mpmath より短桁 x で速い
  2. 近似誤差モデルが gamma 全域で1つ。精度が x に対して一様で、 手法の継ぎ目(Arb の x≈2×10⁷ の谷のような)が存在しない。テスト・保守面積が小さい
  3. 項比が有理数という一点から、短桁の準線形(BSM)・フル桁の p²(BSGS)・ 巨大 x の倍数公式が全部導かれる。1つの式 + 評価戦略の分岐、という構造
  4. 指数(オーダー)は標準手法と互角以上: Spouge の p²·log p に対し BSM は p·log³p、 BSGS は p²·loglog p(モデル値)

正直な弱点: C + GMP の Arb には定数倍で負ける(乗算プリミティブ 4〜10× + Ruby の 呼び出し/GC 税 3〜5×)。ただし指数は同じで、ワンショット比較では差が縮む。

6. より速いオーダーの可能性 (future work)

6.1 Bernoulli データ床

full 桁の x を Stirling で p 桁計算するとき、B₂ₖ は項の大きさに応じて p_k ≈ p(1−k/N) 桁必要で、

$$\sum_k p_k \approx \frac{p\cdot N}{2} \approx \frac{p^2}{4\log_{10} p}$$

つまり Bernoulli 係数列を float として実体化する手法は、係数を読むだけで Θ(p²/log p)。 生成をどれだけ速くしても超えられない。sub-quadratic への道は Bernoulli を捨てた側(補間・不完全ガンマ系 = 係数が有理漸化式で生成される側)にしかない。

6.2 p^1.5·polylog への道(多点評価)

BSGS の p² の由来は「バッチ係数を厳密整数で持つ」制約で batch = log p 止まりなこと。 係数を p 桁 float に落とし、batch = m = √n (n ≈ 4p) に取ると:

  1. バッチ多項式 D(z) = Π_{j<m}((x−j)−z), N(z) を係数 p 桁 float の積木で構築 — M(m·p)·log m
  2. z = 0, m, 2m, … の等差数列での多点評価。一般点の remainder tree は float では不安定だが、 等差数列なら Newton(下降階乗)基底変換 + 畳み込みで安定に評価できる。 誤差増幅は 2^O(m) × 値域比 → ガード桁 O(√p·log p) = o(p)
  3. 階乗型 prefactor は m 回の逐次乗法更新

合計 O(p^1.5·polylog p)。整数の世界での完全な先例が Pollard–Strassen (1976) の n! mod N アルゴリズム(p 桁 float ≈ mod 10^p の類推)。近代的整理は Chudnovsky–Chudnovsky / Bostan–Gaudry–Schost(いずれも exact/modular)。 float 版の安定性解析つき実装は私の知る限り世界に存在しない(新規性がある部分)。

よくある誤解: 「係数に x^m が現れて p^1.5 桁に爆発する」— これは厳密表現と float 表現の混同。 x の magnitude は ~4p なので x^m は指数 ~m·log₁₀(4p) の普通の p 桁 float であり、 問題は表現サイズではなく桁落ち量(上記の通り o(p) で抑えられる見込み)。

留意点:

  • 中間形がない: 係数を float 化した瞬間、1点評価では全精度乗算に戻る。 多点評価まで一気に作って初めて利得が出る(オール・オア・ナッシング)
  • 前提条件: 準線形の整数乗算(素の CRuby Integer は GMP なしだと Toom3 で本末転倒。 BigDecimal 自前 NTT に Kronecker で載せるのが正道)。メモリは係数列 p^1.5 桁
  • 効く領域は「フル桁 x × 数万桁以上」のみ。クロスオーバー予想 10⁴〜10⁵ 桁、10⁵ 桁で ~10倍
  • 成立条件: 誤差台帳の文書化・遅い経路を oracle にした敵対的テスト・kill switch の3点セット

なお Γ(x) は x について holonomic でない(線形微分方程式を満たさない)ため、 bit-burst 系の準線形評価は適用できない。p^1.5 が現実的なフロンティアと思われる。

7. 実測まとめ

同一マシン (Apple Silicon)。「旧」= Spouge 実装、「新」= 本方式(整数化 BSM 統合後, 2026-07)。

旧実装との比較

計算 旧 (Spouge) 経路
gamma(1.25, 10⁴) 49s 0.24s BSM
gamma(1.125, 10⁵) ~6000s (推定) 2.1s BSM
gamma(1/3 フル桁, 10⁴) 56s 8.2s BSGS
gamma(1/3 フル桁, 10⁵) ~7000s (推定) 800s BSGS
gamma(10¹⁷, 10⁴) 71s 1.0s doubling
gamma(10¹⁷, 10⁵) ~9000s (推定) 13s doubling

mpmath (gmpy backend) との比較

計算 mpmath 初回 mpmath キャッシュ後
gamma(1.25) 10⁴ 26.9s 0.69s 0.24s
gamma(1.25) 2×10⁴ 226s 3.7s 0.57s
gamma(1/3) 2×10⁴ 226s 3.8s 33s

短桁 x ではキャッシュ済み mpmath より速い(ステートレスのまま)。フル桁 x では キャッシュ済み Stirling に負ける(演算数の差 + 実装言語)。

Arb (FLINT, python-flint) との比較 (dps=10⁵)

計算 Arb 備考
gamma(1.125) 0.27s 2.1s 乗算プリミティブ差 (GMP は BigDecimal NTT の 4〜10×) + Ruby 税
gamma(√2) 10⁴桁 0.1s 7.8s フル桁。Arb は rectangular splitting
gamma(2×10⁶) 0.19s 2.3s Arb は厳密階乗 (コスト ∝ x)
gamma(2×10⁷) 3.7s (初回) / 1.6s 3.4s Arb の戦略境界の谷 + cold Bernoulli では互角
gamma(2×10⁸) 1.3s 3.9s Arb は Stirling (x↑ で軽くなる)、新は doubling (∝ log x)

指数は互角、定数は「基礎演算 4〜10×」×「Ruby の呼び出し固定費 + GC 3〜5×」の積で説明できる (プロファイル実測: doubling 経路は Integer#* (GMP) 48%、GC は整数化後 ~8%)。

8. 参考文献

  • J.L. Spouge, "Computation of the gamma, digamma, and trigamma functions" (1994)
  • R.P. Brent, "Unrestricted algorithms for elementary and special functions" (IFIP 1980) — 不完全ガンマ級数による Γ
  • D.M. Smith, "Algorithm 814: Fortran 90 software for floating-point multiple precision arithmetic, gamma and related functions" (ACM TOMS, 2001)
  • F. Johansson, "Arbitrary-precision computation of the gamma function" (2021) — 既存手法の包括的サーベイ
  • F. Johansson, "Evaluating parametric holonomic sequences using rectangular splitting" (ISSAC 2014)
  • J.M. Pollard (1974) / V. Strassen (1976) — n! mod N の √n·polylog 算法(§6.2 の原型)
  • A. Bostan, P. Gaudry, É. Schost, "Linear recurrences with polynomial coefficients and application to integer factorization and Cartier–Manin operator" (2007)
  • R.P. Brent, P. Zimmermann, "Modern Computer Arithmetic" — M(n) 表記・基数変換・特殊関数の教科書

gamma 計算の議論メモ (2026-07, Claude とのセッション)

このブランチ (gamma_lagrange) の実装検証と、その先の話 (n^1.5 化など) を再開するためのメモ。 自己完結版(発表・merge 後の参照用)は gamma_algorithm.md — 旧実装/新実装/既存手法/p^1.5 の可能性/実測まとめを diff なしで読める形にしてある。こちらはセッション時系列の生メモ。

2026-07-06 更新: bsm_prod の Integer 化 + BSM の統合 (gamma_lagrange_n_plus_half 削除、10^f スケール一般化、exp2 チャネル) を実装済み。 gamma(1.125, 1e5): 4.1→2.1s / doubling 各点 5〜15% 改善 / BSGS 不変 / 精度は全経路で満額を再検証済み。 注意: rake compile が lib を tmp/*/stage/lib にコピーするため、lib 編集後の測定は -Ilib を先に置くか再 compile しないと旧コードを測る(実際に一度踏んだ)。

1. 実装の検証状況

検証済み (すべてパス):

  • 数式の再導出: barycentric 係数漸化式 / 再構成式 / 倍数公式の帳簿 (large! が再帰で2乗になり 2^i 指数で積み上がる構造) / l 見積りの不動点反復 (初期値 l=prec から根の上側に単調収束するので過大評価側で安全)
  • 恒等式クロスチェック: 漸化式 gamma(x+1)=x·gamma(x)、反射 gamma(1/4)gamma(3/4)=π√2、倍数公式、lgamma=log(gamma)、符号
  • mpmath との 1000 桁突き合わせ: 1/3, 0.3, 1e8+1/3, 5+1e-40, -987.654321, 0.5001, lgamma(1e18), lgamma(1e400)
  • パス別高精度セルフチェック: BSM gamma(1.25, 20000) 誤差指数 -20000 (満額)、BSGS gamma(1/3, 5000) -4999 (ulp 級、prec 990 でも -989 でドリフトなし)、doubling gamma(1e15+0.5, 5000) -5000

再現方法: mpmath (venv に pip install) で高精度参照値を出して突き合わせ。恒等式チェックは 「gamma(x+1, p) vs x·gamma(x, p+10)」「gamma(x, p) vs gamma(x, p+300) の truncate 比較」の形。 比較時の除算は / でなく .div(x, prec) を使うこと (/ は巨大 exponent で NoMemoryError — 有効桁から結果精度が決まる仕様由来、既知)。

2. 明文化した invariant (コメント済み、壊すと静かに死ぬ)

  • batch_prod 同一値相殺 (BSGS): x がノード直近のとき batch_prod は激しく桁落ちするが、同じ計算値を sum の除算と prod の乗算の両方に使うので最終 prod·sum では厳密相殺。batch_prod を再計算・並べ替えするリファクタはノード直近入力だけを壊す。回帰テスト: gamma(5 + 1e-40) (test_bigmath.rb)
  • factorial_power_part の単調性 (integer_factorial_parameter): 深い再帰レベルほど b が小さく l が大きいので非減少。fact_y の増分更新はこれに依存
  • bit 落としマージン (n_plus_half): 保持ビット (prec·10+192)/3 = prec·log2(10) 超 + 64 bits。マージ1段 ~1 bit 損失 × 木の深さ log2(2l) を吸収
  • 精度予算の分散ヒューリスティック一覧: EXTRA_PREC=16 / l の +10 / b = 2·prec (経験的最適、正の下限は ~1.36·prec) / internal_xn_prec = prec + log10(b+l)·batch / 上記 +64 bits。どれかを「改善」するとテスト帯域 (200–1200桁) では通るが大 prec で末尾が欠ける、が典型的な壊れ方

3. 計算量の整理

  • 表記の流儀: bit complexity を M(n) でパラメータ化し、unbalanced 積は M(m,n) = (n/m)·M(m) = n·log(m) と数える (Brent–Zimmermann 流)。この前提で BSM O(p·log³p) / BSGS O(p²·loglog p)
  • 実装の文字通りの漸近: 小オペランド (係数 ~log²p 桁) は BigDecimal の schoolbook パス (NTT 閾値 450 words 未満) なので O(p²·log²p)、ただし係数 1/(word桁数)² で実測は p²。schoolbook の方が実サイズでは速いための選択であり、主張は理論値でよい (全ライブラリ共通の慣習)
  • Spouge との名目比較: 実装同士だと 1 log 悪い (p²log²p vs p²logp) が、クロスオーバーは p ~ 10^150 桁の彼方
  • ベルヌーイ実体化ルートの下限: Stirling で full-digit x を p 桁計算するとき B_2k は p_k ≈ p(1−k/N) 桁必要で Σp_k ≈ p²/(4·log10 p)。係数列を「読むだけ」で Θ(p²/log p) — 生成をいくら高速化しても超えられない。full-digit x の sub-quadratic はベルヌーイを捨てた側 (補完系/不完全ガンマ系) にしか存在しない。この観察が n^1.5 化の動機の核

4. n^1.5·polylog への拡張 (未着手、原理的障害なしと判定)

骨子: BSGS の batch 係数を厳密整数 (batch=log p 止まりの原因) から p桁 float に落とし、 batch = m = √n として batch 多項式 D(z)=Π((x−j)−z), N(z) を積木で構築、 z = 0, m, 2m, ... の等差数列多点評価で全 batch を一括評価する。Pollard–Strassen (n! mod N) の float 版。

  • 「x^m が p^1.5 桁に爆発」(gemini の反論) は誤り: 桁数と指数の混同。x の magnitude は ~4p なので x^m は指数 ~m·log10(4p) の普通の p桁 float。切り詰めで正しい
  • 真の論点は2つ:
    • (a) 展開評価の桁落ち: ~m·log10(4p) + q 桁 (q = −log10|x−最近接ノード|)。ガード O(√p·log p)、q は同一値相殺トリックで処理
    • (b) fast multipoint evaluation の float 安定性: 一般点集合の remainder tree は不安定で使えないが、評価点が等差数列なので Newton (下降階乗) 基底変換 + 畳み込み (Aho–Steiglitz–Ullman 系) が使える。増幅 2^O(m) × 値域比 → ガード O(√p·log p)。ここの誤差解析が本体で、文献に存在しない (新規性)
  • コスト: 積木・多点評価 M(m·p)·log m ≈ p^1.5·log²p、prefactor 逐次更新 p^1.5·log p、結合 m·M(p)。計 O(p^1.5·polylog)
  • 中間形なし: float 係数にした瞬間 1 点評価では full-prec 乗算に戻るので、多点評価まで一気に作って初めて利得。オール・オア・ナッシング
  • 実装の前提条件: 準線形整数乗算。素の CRuby Integer は Toom3 で M(p^1.5)=p^2.2 になり本末転倒 → BigDecimal 自前 NTT に Kronecker で載せる (仮数配列を固定小数点ベクタに流用、C 側に多項式乗算入口)。メモリは係数列 p^1.5 桁 (1e5桁で数百MB)
  • 効く領域: full-digit x × p ≳ 数万桁のみ。クロスオーバー予想 1e4–1e5、1e5 で ~10倍、以後 √p/polylog で開く
  • メンテ可能性の条件 (これがないなら作らない方がよい): ①誤差台帳文書 (段階ごとのガード桁と根拠) ②遅いパスを oracle にした敵対的テスト (ノード直近・整数±1e-k・full-digit) ③fast path の kill switch
  • 最初の一歩: GMP 環境の素 Integer で m=数十, p=数千の玩具プロトタイプを書き、ガード桁の実測カーブを取る (黒なら早期撤退)
  • 文献アンカー: Pollard–Strassen 1976 / Chudnovsky–Chudnovsky BSGS / Bostan–Gaudry–Schost (いずれも exact/modular)。Johansson rectangular splitting (ISSAC 2014) は非スカラー乗算回数を減らすだけで bit cost は落とさない。float 版安定性解析つきの実装・論文は見当たらない

5. Arb (python-flint) 比較の実測 (2026-07, このマシン)

  • 乗算単体 BigDecimal NTT vs Ruby Integer (GMP): 10.8× / 5.5× / 3.6× @ 1e4/1e5/1e6 桁 (32bit DECDIG の packing 密度 + asm 差込み。Toom 追加で埋まるのは一部)
  • プロファイル: BSM (1.125, 1e5) は BigDecimal#mult 55% + add 10% + GC 25% (小 mult ~1e6 回の呼び出し固定費と GC が主犯)。doubling (2e6, 1e5) は Integer#* 48% (GMP) + BigDecimal 25% + GC 13%
  • Arb の整数 gamma の正体 (x スイープ @ dps 1e5): x ≤ 1e7 でコスト ∝ x (厳密階乗パス: 0.08→0.9s for 1e6→1e7)、x ≥ 4e7 で ~1.3–1.5s (Stirling、x↑で微減)。x ≈ 2e7 が crossover で、初回はベルヌーイ生成 ~2s を追加で払う (3.67s→2回目 1.62s)。「なぜか arb が遅いエッジケース」= 両戦略の谷間 × cold cache
  • BigMath doubling は log x スケール: 2.64 / 3.7 / 4.61s @ 2e6/2e7/2e8。x=2e8 では warm Arb 1.27s に 3.6× 負ける (Stirling は x↑ で安くなるため)。doubling が構造的に勝つのは「ベルヌーイを払えない一発勝負」の状況
  • 総合: 差 = 基礎演算 4–10× × Ruby層 (呼び出し固定費+GC) 3–5× × アルゴリズム定数 1.5–3×。指数は互角
  • 改善メニュー (効果測定済み):
    • 整数 x の厳密階乗パス実施済み相当: int_bsm_prod 化で直接パスが Integer になり吸収 (2026-07-06)
    • integer_factorial 直接パスの Integer 化実施済み (同上)
    • 残ノブ: doubling 閾値 4 * prec → 正しい形は n·log(n) < K·prec(実測で K を決める。32@1e5 実験は K′≈210 相当)
    • 残ノブ: BSM/BSGS crossover 定数(BSM が整数化で速くなったので BSGS 側に動くはず)
    • BSM/BSGS の merge を C カーネル化 or destructive 演算で alloc/GC 削減
    • 64bit DECDIG 化・mid-size Toom (full-digit の 60× のうち 2–4× ぶん、n^1.5 化とは独立に効く)
    • BSM 残余の主役は power(e, prec) の exp/log 系(統合後プロファイルより)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment