第6章: 高速除算アルゴリズム

難易度: 中級

Fast Division Algorithms — Knuth D, Burnikel-Ziegler, and Newton Iteration

多倍長整数の除算は、乗算より実装が難しく定数倍も大きい(漸近計算量は、6.3・6.4 節で見るとおり乗算と同じ $O(M(n))$ にできる)。乗算はすべての桁の積和で定まるため並列化しやすいが、 学校式の除算は「商の各桁を推測し、間違っていたら修正する」という逐次的な構造を持つ。 高度に最適化された実装でも、除算は乗算の 2〜5 倍遅いのが一般的である。

本章では、学校式除算を体系化した Knuth Algorithm D から始めて、 分割統治による再帰除算 (Burnikel-Ziegler)、 除算を乗算に還元する Newton 逆数反復、 余りが $0$ と分かっているときの特殊実装 Exact Division まで扱う。 sangi の実装を参照しつつ、サイズごとにどのアルゴリズムが最速かを定量的に把握する。

6.1 学校式除算 (Knuth Algorithm D)

$n$ ワードの被除数 $A = (a_{n-1}, \ldots, a_0)$ を $m$ ワードの除数 $B = (b_{m-1}, \ldots, b_0)$ で割り、 商 $Q$ と余り $R$ ($A = QB + R$, $0 \le R < B$) を求める。 Knuth の The Art of Computer Programming Vol. 2 §4.3.1 に記述される Algorithm D は多倍長整数除算の代表的な基本アルゴリズムであり、高速な再帰除算においても小サイズの基底ケースとして重要な役割を果たす。

Algorithm D の概要

  1. 正規化: $b_{m-1}$ の最上位ビットが $1$ になるよう $A, B$ を同じ量だけ左シフト。
  2. 反復 ($j = n-m, n-m-1, \ldots, 0$):
    1. 試しの商 $\hat{q}$ を被除数の上位 2 ワードと除数の最上位ワードから推測し(6.2 節の 2-by-1)、除数の 2 番目のワード $b_{m-2}$ まで見て大きすぎる $\hat{q}$ を減らす(多くとも 2 回)。
    2. $A \leftarrow A - \hat{q} \cdot B \cdot \beta^j$ を計算 ($\beta = 2^{64}$)。
    3. 負になったら(まれ)$\hat{q} \leftarrow \hat{q} - 1$、$A \leftarrow A + B \cdot \beta^j$ で足し戻す。
    4. $q_j \leftarrow \hat{q}$ を記録。
  3. 脱正規化: 余りを右シフトで元に戻す。

計算量は $O(nm)$ 回のワード乗算である。$n = m$ のバランス除算では $O(n^2)$、 これは学校で習う筆算と本質的に同じ計算手順である。

なぜ正規化が必要か

$b_{m-1}$ の最上位ビットが $1$ のとき、上位 2 ワードからの試しの商 $\hat{q}$ は真の商 $q$ と $q \le \hat{q} \le q + 2$(誤差 $\hat{q} - q \in \{0, 1, 2\}$)の関係にある (Knuth Theorem B)。したがって補正は多くとも 2 回で済む。 この推測だけでは $\hat{q} \ne q$ がおよそ 3 回に 1 回起きるが、$b_{m-2}$ まで見て絞り込めば、 減算後の足し戻しが必要になる確率はおよそ $2/\beta$ 以下になり、$\beta = 2^{64}$ では約 $10^{-19}$ ときわめてまれである。 正規化なしでは推測誤差が大きくなり、補正が多発して性能が劣化する。

sangi では MpnOps.hpp の div_basecase / sbpi1_div_qr がこの役割を担う。基本構造は次の通りである。

// MpnOps.hpp の div_basecase (概略)
inline void div_basecase(uint64_t* q, uint64_t* a, size_t an,
                         const uint64_t* b, size_t bn) {
    const uint64_t d1 = b[bn - 1];   // MSB = 1 (正規化済み)
    const uint64_t d0 = b[bn - 2];
    // 事前計算: dinv = ⌊(β³ − 1) / (d1·β + d0)⌋ − β  (Möller-Granlund の 3/2 逆数、invert_pi1)
    // 反復: 各位置 j で 3-by-2 商推測 + addmul_1 による減算 + 補正
}

6.2 商の推測 — 2-by-1 と 3-by-2

Algorithm D の内ループは「$A$ の上位 3 ワードと $B$ の上位 1〜2 ワードから $\hat{q}$ を推測する」ことに尽きる。 計算時間のほとんどはこのワード単位の除算に費やされるため、この微小な操作の最適化が全体性能を左右する。

2-by-1 の推測

最も単純な推測は上位 2 ワードからの商である:

$$\hat{q} = \min\!\left(\left\lfloor \dfrac{u_{n+1}\beta + u_n}{v_{n-1}} \right\rfloor,\; \beta - 1\right).$$

これは 1 回のハードウェア除算 (DIV 命令) で計算できる。x86-64 の DIV r64 は 128-by-64 bit 除算を約 $20\text{-}30$ サイクルで実行する。

3-by-2 の推測 (Möller-Granlund)

より精度の高い推測として、上位 3 ワードを用いる方法がある。 Möller & Granlund (2011) は、事前計算した逆数 $\text{dinv}$ を使って、 ハードウェア除算を完全に除去した 3-by-2 推測を提示した:

$$\text{dinv} = \left\lfloor \dfrac{\beta^3 - 1}{d_1 \beta + d_0} \right\rfloor - \beta, \quad (q_1, q_0) = u_2 \cdot \text{dinv} + (u_2, u_1).$$

導出の詳細:$\text{dinv}$ の式はどこから来るか

Step 1(目標):正規化済みの 2 ワード除数 $D = d_1 \beta + d_0$($D \in [\beta^2/2, \beta^2)$)で、3 ワードの $U = (u_2, u_1, u_0)$(ただし $(u_2, u_1) < D$)を割った商 $q = \lfloor U / D \rfloor$ は 1 ワードに収まる。$\beta^3 / D \in (\beta, 2\beta]$ なので、スケールを $\beta^3$ にして $D$ の逆数を整数で表したい。

Step 2(3/2 逆数):$V := \lfloor (\beta^3 - 1)/D \rfloor$ とおく($\beta^3 - 1$ を使うのは $V D \le \beta^3 - 1 < \beta^3$ を保証するため)。$V \in [\beta, 2\beta - 1]$ なので、$\text{dinv} := V - \beta \in [0, \beta)$ は 1 ワードに収まる。

Step 3(作り方):$\text{dinv}$ は、$d_1$ だけの逆数 $\lfloor (\beta^2 - 1)/d_1 \rfloor - \beta$(2/1 除算用。$\lfloor (\beta^2 - 1)/d_1 \rfloor$ は $[\beta + 1, 2\beta - 1]$ にあり、$d_1 = \beta/2$ で $2\beta - 1$)を出発点に、$d_0$ を使った 2 段の補正で求める(Möller-Granlund の Algorithm 6、sangi の invert_pi1)。2/1 用の逆数をそのまま使うと、候補が真の商から大きく外れる入力がある。

Step 4(推測式):$U V / \beta^3 \approx (u_2 \beta + u_1)(\beta + \text{dinv}) / \beta^2$ の主要項 $u_2 + \lfloor (u_2\,\text{dinv} + u_1)/\beta \rfloor$ を取り出したのが上の $(q_1, q_0)$ 加算式である。候補 $q_1 + 1$ は真の商と $\{-1, 0, +1\}$ しか違わず、余りの判定で確定する(Möller-Granlund, Theorem 3)。$\square$

この MULX + 加算で $\hat{q}$ の推測が $2\text{-}3$ サイクルで済む。 事前計算した $\text{dinv}$ は除数が変わらない限り再利用できるため、長い除算では圧倒的に有利である。

// sangi: 3-by-2 商推測 (MpnOps.hpp より)
inline void udiv_qrnnd_3by2(uint64_t& q, uint64_t& r1, uint64_t& r0,
                            uint64_t u2, uint64_t u1, uint64_t u0,
                            uint64_t d1, uint64_t d0, uint64_t dinv) {
    uint64_t p_hi;
    uint64_t p_lo = _umul128(u2, dinv, &p_hi);
    uint64_t q0 = p_lo + u1;
    uint64_t q1 = p_hi + u2 + (q0 < p_lo);
    // 以下: 商の条件付き補正 (多くとも 2 回)
}

推測誤差の上界

$\text{dinv} = \lfloor (\beta^3 - 1)/(d_1 \beta + d_0) \rfloor - \beta$ は、言わば 「除数の上位 2 ワード $D$ の逆数を $\beta$-進で $1 + \text{dinv}/\beta$(のスケール)で近似した」形である。 これで作った候補 $q_1 + 1$ は、真の商 $q$ と $\{-1, 0, +1\}$ しか違わない (Möller-Granlund, Theorem 3)。1 回目の補正(候補が 1 大きい)は入力によって頻繁に起き分岐予測が効きにくいが、 2 回目の補正(候補が 1 小さい)はごくまれである。 なお、多倍長の商の 1 桁を「被除数の上位 3 ワード ÷ 除数の上位 2 ワードの厳密な商」で推測すると、 誤差 $\hat{q} - q$ は $\{0, 1\}$ に収まり、2-by-1 推測の $\{0, 1, 2\}$ より狭い(Knuth)。

6.3 Burnikel-Ziegler 再帰除算

Knuth D は $O(n^2)$ のワード乗算を要する。 サイズが数百 ワードに達すると、乗算を内部で高速化しても全体が $O(n^2)$ に縛られる。 Burnikel & Ziegler (1998) は、分割統治により除算のコストを乗算で表せるようにした(乗算の速さに応じて $O(M(n))$ または $O(M(n) \log n)$。下の漸化式を参照)。 ここで $M(n)$ は $n$ ワード乗算のコスト (Karatsuba 以降の高速乗算が使える)。

Div-2n-by-n パターン

被除数 $A$ が $2n$ ワード、除数 $B$ が $n$ ワードで、$A / B$ の商が $n$ ワードに収まる場合を考える。 $B$ と $A$ をそれぞれ $n/2$ ワード単位で分割する:

$$B = B_1 \beta^{n/2} + B_0, \quad A = A_3 \beta^{3n/2} + A_2 \beta^{n} + A_1 \beta^{n/2} + A_0.$$

このとき、商を上下 2 つに分けて順に再帰的に求めることができる:

  1. 上半分の商 $Q_1$: $A$ の上位 $3n/2$ ワード $(A_3, A_2, A_1)$ を $B$ で割る($3n/2$-by-$n$)。中身は、上位 $n$ ワード $(A_3, A_2)$ を $B_1$ で割る半サイズの再帰($n$-by-$n/2$)で仮の商 $\hat{Q}_1$ を得て、$A_1$ を下ろして $\hat{Q}_1 B_0$ を引き、負なら $B$ を足し戻す補正(最大 2 回)である。
  2. 下半分の商 $Q_0$: 中間余り $R_1$ と $A_0$ を並べた $3n/2$ ワードを、同じ手順で $B$ で割る。
  3. 結合: $Q = Q_1 \beta^{n/2} + Q_0$。

再帰の各段で、サイズが半分の除算($n$-by-$n/2$)2 回と、$\hat{Q} B_0$ を作る $n/2 \times n/2$ 乗算 2 回が発生する。乗算コストを $M(n)$ とすると:

$$D(2n, n) = 2\, D(n, n/2) + O(M(n)).$$

導出の詳細:再帰の正当性

Step 1(上半分 $Q_1$):$A$ の上位 $3n/2$ ワード $A_{\text{hi}} = A_3 \beta^{n} + A_2 \beta^{n/2} + A_1$(すなわち $A = A_{\text{hi}} \beta^{n/2} + A_0$)を $B$ で割って商 $Q_1$ と部分余り $R_1$ を得る。これは $(A_3, A_2)$ を $B_1$ で割る半サイズの再帰と、$\hat{Q}_1 B_0$ を引く補正で行う(上の 1.)。

Step 2(補正と中間余り):$Q_1$ を求めた時点で、まだ $A$ の下半分は処理していない。新しい被除数を

$$\displaystyle A' = R_1 \cdot \beta^{n/2} + A_0$$

とおく。$A' < B \cdot \beta^{n/2}$ が再帰の前提条件で、$Q_1$ を $A_{\text{hi}}/B$ の真の商に揃えることでこれを満たす(補正は最大 2 回の $B$ 加減算)。

Step 3(下半分 $Q_0$ と結合):$A'$ を $B$ で割って $Q_0$, $R$ を得る。すると

$$\displaystyle A = Q_1 \beta^{n/2} \cdot B + A' = Q_1 \beta^{n/2} \cdot B + Q_0 \cdot B + R = (Q_1 \beta^{n/2} + Q_0) \cdot B + R.$$

よって $Q = Q_1 \beta^{n/2} + Q_0$、余り $R$ が一致。$\square$

再帰内部で発生する $\hat{Q}_1 B_0$ の計算が $n/2 \times n/2$ 乗算 $O(M(n))$ である。

この漸化式の解は $M(n)$ の増大の速さで変わる。$M(n) = \Theta(n^{\alpha})$($\alpha > 1$。Toom-3 なら $\alpha \approx 1.465$)のときは、各段のコストが等比級数的に減るので Master 定理の第 3 の場合にあたり $D(n) = O(M(n))$、すなわち除算は乗算と同じ漸近計算量になる。 一方 $M(n) = \Theta(n \log n)$(FFT)のように準線形のときは第 2 の場合にあたり $D(n) = O(M(n) \log n) = O(n \log^2 n)$ で、乗算より $\log n$ 倍だけ遅い。

sangi の実装

// MpnOps.hpp: dcpi1_div_qr_n (Divide-and-Conquer, pre-inverted)
inline uint64_t dcpi1_div_qr_n(uint64_t* qp, uint64_t* np,
                               const uint64_t* dp, size_t n,
                               uint64_t dinv, uint64_t* scratch) {
    if (n < DC_DIV_QR_THRESHOLD) {
        return sbpi1_div_qr(qp, np, 2 * n, dp, n, dinv); // 基底: 学校式
    }
    size_t lo = n / 2, hi = n - lo;
    // 上半分: 2hi-by-hi 再帰
    uint64_t qh = dcpi1_div_qr_n(qp + lo, np + 2 * lo, dp + lo, hi, dinv, scratch);
    // 補正: Q1 × B_low の差し引き、下半分の再帰
    // ...
}

sangi では 2 つの閾値を区別している (値は 2026-05-08 時点):

  • BZ_THRESHOLD = 24: 最上位ディスパッチの切替点。除数が 24 ワード未満なら Knuth D 直接、24 ワード以上なら BZ 再帰に入る。
  • DC_DIV_QR_THRESHOLD = 20: BZ 再帰内部の基底ケース閾値。再帰でサイズが 20 ワード未満まで縮んだら sbpi1_div_qr (学校式) に落とす。

入口閾値 24 と基底閾値 20 の差は小さいが、両者を独立に持つことで「24〜31 ワードあたりで BZ に入ると、最初の再帰分割で 12〜16 ワードになり、基底閾値 20 を下回って学校式に切替わる」自然な遷移になる。なお BZ_THRESHOLD は当初 64 だったが、2026-05-08 に sbpi1 (学校式) ループの高速化を反映して 24 に下げられた。

Burnikel-Ziegler の 2n-by-n 除算を 2 回の 3n/2-by-n 除算(それぞれ n-by-n/2 の再帰を 1 回含む)に分解する図 Burnikel-Ziegler 分割統治 A₃ A₂ A₁ A₀ 被除数 A (4 ブロック × n/2 ワード) B₁ B₀ 除数 B (2 ブロック × n/2 ワード) 再帰 1: (A₃, A₂, A₁) ÷ B → Q₁ 再帰 2: (R₁, A₀) ÷ B → Q₀ Q = Q₁βn/2 + Q₀
図1: Burnikel-Ziegler の 2n-by-n 除算は、上半分の再帰で $Q_1$、続いて部分余り $R_1$ と $A_0$ から下半分の再帰で $Q_0$ を求め、$Q = Q_1 \beta^{n/2} + Q_0$ と結合する。各再帰は半サイズの除算 1 回と $O(M(n))$ の乗算 1 回を含む。

6.4 Newton 逆数反復

除算 $A / B$ を 2 段階に分ける:

  1. $B$ の逆数近似 $X \approx 1/B$ を高速に求める。
  2. $A \cdot X$ で商を計算し、誤差を補正する。

このアプローチでは、除算を 乗算に還元できる。 乗算 $M(n)$ が FFT 等で $O(n \log n)$ なら、除算も同じオーダーになる。 Newton 反復はこの戦略の要である。

Newton 反復式

$f(x) = 1/x - B$ の零点 $x = 1/B$ に対して Newton 法を適用する:

$$x_{k+1} = x_k - \dfrac{f(x_k)}{f'(x_k)} = x_k (2 - B x_k).$$

導出の詳細:反復式と二次収束

Step 1(反復式):$f(x) = 1/x - B$ より $f'(x) = -1/x^2$。Newton 法 $x_{k+1} = x_k - f(x_k)/f'(x_k)$ に代入すると

$$\displaystyle x_{k+1} = x_k - \dfrac{1/x_k - B}{-1/x_k^2} = x_k + (1/x_k - B) x_k^2 = x_k + x_k - B x_k^2 = x_k(2 - B x_k).$$

除算が消える点が肝心($1/B$ を求めるのに $B$ で割らない)。

Step 2(二次収束):誤差を $\varepsilon_k = 1 - B x_k$ とおくと $B x_k = 1 - \varepsilon_k$、よって

$$\displaystyle B x_{k+1} = B x_k (2 - B x_k) = (1 - \varepsilon_k)(2 - (1 - \varepsilon_k)) = (1 - \varepsilon_k)(1 + \varepsilon_k) = 1 - \varepsilon_k^2.$$

つまり $\varepsilon_{k+1} = \varepsilon_k^2$。誤差が毎反復で 2 乗される。$\square$

$x_k$ が $1/B$ に近いとき $B x_k \approx 1$、$2 - B x_k \approx 1$ で、 $x_{k+1}$ の誤差は $x_k$ の誤差の 2 乗に比例する (二次収束)。 精度 $b$ ビットが必要なら、初期近似 $x_0$ (たとえば倍精度浮動小数。53 ビットの仮数を、丸めの余裕を見て 48 ビット正しいと数える) から始めて $\lceil \log_2(b / 48) \rceil$ 回の反復で十分。

各反復のコスト管理

$k$ 回目の反復で $x_k$ の精度が $p_k$ ビットなら、 $B x_k$ の計算では $x_k$ を $p_k$ ビットで保持すればよい。 したがって各反復のコストは $M(p_k)$ で、合計は等比級数:

$$\sum_{k=0}^{K} M(p_k) \le M(n) \cdot \sum_{k=0}^{\infty} 2^{-k} = 2 M(n).$$

つまり、最終精度の乗算 1 回の約 2 倍のコストで逆数が求まる。 これが Newton 反復が「乗算と同じオーダー」で済む秘密である。

sangi の Float 除算での実装

sangi では、Float の除数が 64 ワード (約 4096 ビット) 以上で Newton に切り替わる。 初期近似は倍精度による逆数、反復中は精度を倍々に拡張する。

// Float.cpp: Newton 除算の初期近似
int64_t b_bl = static_cast<int64_t>(b_work.mantissa_.bitLength());
int64_t exp_adj = b_work.exponent_ + b_bl - 1;  // b_norm ∈ [1, 2)
b_work.exponent_ = -(b_bl - 1);
double b_d = b_work.toDouble();                  // 53 bit 近似
Float x(1.0 / b_d);
x.exponent_ -= exp_adj;                          // 元のスケールに戻す
x.effective_bits_ = 64;                          // 精度フィールドの初期化(反復は correct_bits = 48 から数える)

// Newton 反復本体 (b_work は正規化済みの除数)
int correct_bits = 48;
while (correct_bits < target_bits) {
    Float bx = b_work * x;                       // ≈ 1
    Float residual = one - bx;                   // ≈ 2^{-correct_bits}
    x = x + x * residual;                        // x_{k+1} = x_k (2 − B x_k)
    correct_bits = std::min(2 * correct_bits, step_bits);  // step_bits: その反復の作業精度(定義は省略)
}

精度追跡 (effective_bits)

Newton 反復の精度管理は微妙である。$k$ 回目の 目標 精度 $p_k$ と、 実際に達成された 効果的 精度 $\text{effective\_bits}$ は必ずしも一致しない。 反復で用いる丸めの方向、入力誤差の伝播、加減算での桁落ちが原因である。

sangi の実装では、各 Float が effective_bits_ フィールドを持ち、 反復毎に min(2 * correct_bits, step_bits) で更新する。 最終商の計算でも誤差を踏まえた 1 回の補正を行う:

$$Q \approx A \cdot x, \quad \text{residual} = 1 - B \cdot x, \quad Q_{\text{corrected}} = Q + Q \cdot \text{residual}.$$

これは「最終の $M(P, P)$ 乗算を一度節約しつつ誤差を 2 乗で縮める」 fused 補正である。

歴史的補足: 除算アルゴリズムの進化

  • 1962-1973 年: Knuth が The Art of Computer Programming Vol. 2 §4.3.1 で Algorithm D を体系化(初版 1969)。学校式除算の補正条件と正規化の必要性を厳密化。
  • 1971 年: Schönhage-Strassen が FFT 乗算 $O(n \log n \log \log n)$ を提案、除算の Newton 還元と組み合わせて除算の計算量の上界 $O(n \log n \log \log n)$ を与えた。
  • 1993 年: Jebelean が p-進逆元を用いた Exact Division(商の推測と補正が要らない。1 ワード除数なら $O(n)$)を発表。Toom-Cook 補間の小定数除算を高速化。
  • 1998 年: Burnikel-Ziegler が除算専用の分割統治($O(M(n))$、FFT 域では $O(M(n) \log n)$)を確立し、Karatsuba/Toom と組合せて中サイズ帯を埋める。
  • 2011 年: Möller-Granlund が 3-by-2 商推測でハードウェア除算を実質的に除去、現代 CPU 上の小サイズ性能を 2-3 倍改善。
  • 2019 年: Harvey-van der Hoeven が $O(n \log n)$ 整数乗算を達成、除算も同オーダーに(理論進展、実装は研究段階)。

6.5 Exact Division

「余りが $0$ になることがあらかじめ分かっている」除算を特別扱いできる。 たとえば多項式除算での $A = QB$ の解消、二項係数 $\binom{n}{k} = n! / (k!(n-k)!)$ の整数性、 Toom-Cook 補間で出てくる固定除数 (/3, /5, /11 など) — いずれも余りが $0$ の除算である。

Jebelean の Exact Division

余りを切り捨てない普通の除算では商を上位から求める。 しかし余りが $0$ なら、商を 下位から求めても 結果は同じである。 Jebelean (1993) のアイデアは、$B$ の逆元 $B^{-1} \bmod \beta$ を $p$-進的に使うことである(逆元が存在するのは $B$ が奇数、すなわち $\gcd(B, \beta) = 1$ のとき。偶数除数の扱いは下の「divexact_by_odd の威力」を参照):

$$q_0 \equiv a_0 \cdot B^{-1} \pmod{\beta}, \quad A \leftarrow (A - q_0 B) / \beta.$$

これを商のワード数 $n$ 回繰り返せば商が得られる。各ステップは $q_0$ を決める 1 ワード乗算と、 $B$($m$ ワード)の addmul_1 で、商の推測も補正も要らない。Möller-Granlund の逆数推測も不要。結果として:

$$T_{\text{exact}}(n, m) = O(nm), \qquad m = 1 \text{(1 ワードの固定小除数)なら } O(n).$$

用途

  • Toom-Cook 補間: 評価値を Vandermonde 逆行列で戻すとき、固定の小整数で割る (第9章 参照)。 Toom-8 では /3, /5, /7, /11, /13 といった小素数(とその積)での除算が補間に 60 回余り入り、sangi はそれを 8 回の divexact_by_odd に折り畳む。
  • 二項係数: $\binom{n}{k}$ を逐次更新 $\binom{n}{k} = \binom{n}{k-1} \cdot (n-k+1) / k$ で 求めると、各ステップは exact division。
  • Binary Splitting: 有理級数の結合で共通因子の除去 (第10章 参照)。

divexact_by_odd の威力

sangi では Toom-8 実装のなかで divexact_by_odd(c, 3) のような固定除数 exact division を多用する。奇数 $d$ の $\beta$ での逆元 $d^{-1} \bmod \beta$ は 定数として埋め込めるため、全体が addmul_1 ループに折り畳める。 2026-04-19 の Toom-8 の最適化(評価の pre-pad と、この折り畳み)で、524K bit 除算の逆数計算(invert_approx)内の 乗算の合計時間が $11\%$ 縮んだ (684 μs → 609 μs。除算全体の時間は 6.8 節の表を参照)。

なお divexact_by_odd の名の通り、この最適化は除数が奇数であることが前提である。 偶数除数を扱う場合は事前に 2 の冪を右シフトで取り除き、残る奇数部分にのみ適用する。

6.6 特殊定数の除算

2 の冪での除算

$A / 2^k$ は単なる右シフトである。 ワード境界に揃っていれば ($k$ が 64 の倍数) ポインタの付け替えだけで済む。 揃っていなくても各ワードを SHRD 命令で 2 ワードまたぎにシフトすればよい。 コストは $O(n)$ の線形時間だが、乗除算と違って分岐も補正もない。

10 の冪での除算

10 進出力や有効桁管理では $A / 10^k$ が頻繁に必要になる。 $10^k \mid A$ が分かっているとき(末尾の 10 進ゼロを落とす等)は、$10 = 2 \cdot 5$ なので $A / 10^k = (A / 2^k) / 5^k$。右シフト後に 固定除数 $5^k$ で exact division すればよい。 一般の $\lfloor A / 10^k \rfloor$(基数変換や桁の切り捨て)は割り切れないので exact division は使えない(黙って誤った商を返す)。 商と余りを通常の除算で求めるか、$5^k$ が 1 ワードに収まるなら下の小定数での除算を使う。

小定数での除算

$d \in \{3, 5, 7, \ldots\}$ のような小定数での除算は、 Granlund–Montgomery (1994) の division by invariant integers 法で実装する($a$ は 64 ビット、$\ell = \lceil \log_2 d \rceil$):

$$\hat{q} = \lfloor a \cdot \mu / 2^{64+\ell} \rfloor, \quad \mu = \lfloor 2^{64+\ell} / d \rfloor.$$

$\mu$ は事前計算できるので、実行時は 1 回の MULH (上位半分の乗算) とシフト。 最大で 1 回の補正が必要だが、それを含めても 1 ワード除算の数倍速い。

6.7 アルゴリズム選択の閾値 — sangi の設計

高速アルゴリズムには、基本的に「定数項が大きいが漸近的に優位」という性質がある。 小さい入力では素朴なアルゴリズムの方が速いため、サイズごとにディスパッチする必要がある。 sangi の除算ディスパッチは次の通り。

sangi の除算閾値 (2026-05 時点)

除数サイズ $b_n$ (ワード) アルゴリズム 計算量
$b_n < 24$ Knuth D + Möller-Granlund 3-by-2 (div_basecase) $O(n \cdot b_n)$
$24 \le b_n < 5000$ (balanced) Burnikel-Ziegler (dcpi1_div_qr_n) $O(M(n))$(FFT 域では $O(M(n) \log n)$)
$b_n \ge 5000$ (balanced) $\mu$-div (逆数近似 + Barrett)、MU_DIV_BALANCED_THRESHOLD $O(M(n))$
不均衡 $k = n/b_n$ に応じた 3 段階 $\mu$-div: $k \ge 8$ で $b_n \ge 150$、$k=4{-}7$ で $b_n \ge 500$、$k=3$ で $b_n \ge 2500$ $O(M(n))$
Float, $b_n \ge 64$ Newton 逆数反復 (NEWTON_DIV_THRESHOLD = 64) $O(M(n))$
Exact div (固定小除数) Jebelean / divexact_by_odd $O(n)$

なぜ Int では $\mu$-div を使い Float では Newton を使うのか — Int の商は整数のため 余り $R$ を同時に計算しなければならず、Barrett 還元ベースの $\mu$-div が向いている。 一方 Float は商だけを必要とし、厳密な余りを返す必要がなく必要な精度まで近似できればよいため、Newton が自然である。

閾値の調整は経験的

「いつ BZ に切り替えるか」の正確な値は CPU のキャッシュサイズ、 乗算ルーチンの定数、コンパイラ最適化に依存する。 sangi の 24 ワードは Zen 3 + GCC/MSVC Release での測定値であり、 別の CPU では別の値が最適になる。 小さすぎると再帰のオーバーヘッドが学校式の $O(n^2)$ 優位を食い潰し、 大きすぎると漸近的に劣る $O(n^2)$ がいつまでも効いて遅くなる。 実用ライブラリではプラットフォーム依存の閾値を自動チューニングで決定するのが一般的である。

他ライブラリの参考

多倍長整数除算の代表的実装として GMP がデファクト標準であり、 Knuth D + Möller-Granlund 3-by-2 / Burnikel-Ziegler / Newton 還元の組合せを基本とする。 本章で扱った主要アルゴリズムはどれも GMP に採用されており、 sangi もこの系譜に属する。sangi の差別化点は乗算層に 5-smooth NTT(サイズ $2^a 3^b 5^c$)と Toom-8 までの多段戦略を採用している点である(第10章 参照)。

6.8 実測性能

sangi の除算を代表的なサイズで実測し、サイズ帯別の特性を観察する。 測定環境は Zen 3 (Ryzen 5 5600X), MSVC Release x64, single thread。

除算性能 (バランス除算, $n / n$)

サイズ sangi (μs) 主に担当する乗算層
$\le 65$ K bit < 100 学校式 + BZ + Karatsuba
262 K bit 598 BZ + Toom-4 / Toom-6
524 K bit 1834 Newton + Toom-8 + NTT
1 M bit 3850 Newton + 5-smooth NTT

値は 2026-04-19 の Toom-8 最適化 + divexact_by_odd 折り畳み後の実測。

サイズ帯別の実装特性

除算の速度は 除算そのものではなく、内部で呼ばれる 乗算の速度で大部分が決まる。 逆数近似を Newton 反復で作る $\mu$-div では、逆数計算に不均衡乗算 (被乗数 $\gg$ 乗数) が多数発生する。

  • NTT 域 ($n \ge 1200$ ワード): 5-smooth NTT が $O(n \log n)$ の漸近優位を発揮する領域。
  • Karatsuba 域 ($22 \le n < 140$ ワード): 古典的 Karatsuba による $O(n^{1.585})$ が最速。
  • Toom-6/Toom-8 域 (600-1200 ワード): 高次 Toom-Cook が最速。Toom-8 の pre-pad 評価が効く。
  • invert_approx の 2:1 不均衡乗算 (513-1025 ワード): Toom-4,2 特化で不均衡のピースサイズを揃える。

つまり「除算を速くする」には「除算の内側の乗算を速くする」のが本筋である。 524K bit の除算が大きく短縮された要因も、 5-smooth NTT (第10章) の導入と Toom-8 の最適化 (2026-04-19) にあった。

計測上の注意

ベンチマークは CPU の熱状態、メモリ帯域、コンパイラ版に敏感である。 連続実行でサーマルスロットリングが起きると絶対時間が数 $\%$ 悪化する。 sangi のベンチスイートは各サイズの前後に 30 秒の cooldown を入れ、 同じマシン・同じセッションで繰り返し測定することで 絶対値のドリフトを打ち消している。

6.9 まとめ

  • Knuth D と 3-by-2 推測: 小サイズでは学校式 + Möller-Granlund の事前計算逆数が最速。 正規化が前提で、3-by-2 の候補は多くとも 2 回の補正で確定する。
  • Burnikel-Ziegler: 分割統治。$M(n) = \Theta(n^{\alpha})$($\alpha>1$)なら $O(M(n))$、FFT のような準線形乗算なら $O(M(n) \log n)$。 sangi は 24 ワード (BZ_THRESHOLD) から BZ に切り替える。
  • Newton 逆数反復: 除算を乗算に還元、$O(M(n))$ 達成。 二次収束なので正しい桁数が毎回ほぼ倍増し、倍精度の初期値から $n$ ビットまで $\lceil \log_2(n/48) \rceil$ 回程度で到達する(実用サイズでは数回)。 精度追跡 (effective_bits) を誤ると商が壊れる。
  • Exact Division: 余り 0 が保証され、かつ除数が固定の小さな値の場合、Jebelean の $p$-進版で $O(n)$。 Toom-Cook 補間の小定数除算に多用される。
  • 性能の主因は乗算: 除算の速度は大部分「内部で呼ばれる乗算」の速度で決まる。 sangi が 524K bit で達成した改善も、NTT と Toom-Cook の最適化に由来する。

次章では、除算の余りを繰り返し使うモジュラー算術 ($a \bmod m$) を扱う。 RSA や NTT のホットループで現れる Barrett 還元・Montgomery 乗算は、 本章の除算を「除算しない」方向に再構成する技術である。

参考文献

  • Knuth, D.E. The Art of Computer Programming, Vol. 2: Seminumerical Algorithms, 3rd ed., Addison-Wesley, 1997. §4.3.1 (Algorithm D)。
  • Burnikel, C., Ziegler, J. "Fast Recursive Division", MPI-I-98-1-022, Max-Planck-Institut für Informatik, 1998.
  • Möller, N., Granlund, T. "Improved Division by Invariant Integers", IEEE Trans. Computers, 60(2), pp. 165-175, 2011.
  • Granlund, T., Montgomery, P.L. "Division by Invariant Integers using Multiplication", Proc. ACM SIGPLAN PLDI '94, pp. 61-72, 1994.
  • Brent, R.P., Zimmermann, P. Modern Computer Arithmetic, Cambridge University Press, 2010. 第1章。
  • Jebelean, T. "An Algorithm for Exact Division", J. Symbolic Computation, 15(2), pp. 169-180, 1993.
  • Hasselström, K. "Fast Division of Large Integers — A Comparison of Algorithms", Master's thesis, KTH, 2003.

関連章

よくある質問

高速除算はなぜ乗算に帰着されるのか?

除算は逆数の近似値を Newton 法で求め、それを乗算することで実現できる。Newton 法は反復ごとに正しい桁数がほぼ倍増するため、適切な精度制御と高速乗算を用いれば、除算は乗算と同じ漸近計算量 $O(M(n))$ で計算できる。

Barrett還元と Montgomery還元の違いは何か?

Barrett還元は事前に法の逆数近似を計算してシフト・乗算で除算を避ける手法、Montgomery還元は法 $m$ の剰余を $aR \bmod m$($R = 2^k$)の形で表し、$R$ による除算をシフトに置き換えることでモジュラ還元の除算を避ける手法である。いずれも繰り返しモジュラ演算を高速化する。

Exact Division(割り切れる場合の除算)の特別な最適化とは?

除数で割り切れること(余りが 0 であること)が事前にわかっている場合、下位桁から逆元計算で1桁ずつ商を決定できる。通常の除算に必要な商の推測と余りの補正を省けるため、二項係数の逐次計算や Toom-Cook 補間の小定数除算のように、余りが 0 と分かっている場合に有効である(除数の奇数部分に対して使い、2 の冪は右シフトで除く)。