メインコンテンツへスキップ

Go Proposal Weekly Digest

Go言語のproposal更新を毎週お届け

#76821accepted

math/big: add Int.Divide method with rounding modes

ステータス変更: likely_accept accepted

要約

AIによる要約であり、誤りを含む場合があります。

概要

math/big パッケージの Int 型に、丸めモードを指定できる統一的な整数除算メソッド Divide を追加するプロポーザルです。床除算・天井除算・切り捨て除算・最近接偶数丸め除算を一つのAPIで扱えるようにします。

ステータス変更

likely_acceptaccepted
2026年3月11日に "likely accept" となってから1週間、コンセンサスに変化がなかったため、2026年3月18日に @aclements がproposal review groupを代表して正式に "accepted" を宣言しました。

技術的背景

現状の問題点

math/bigInt 型には既に複数の除算メソッドが存在しますが、丸め方式が統一されていません。

  • Quo / QuoRem: ゼロ方向への切り捨て(Go言語の / 演算子と同様のT除算)
  • Div / DivMod: ユークリッド除算(負数でも余りが常に非負)
    しかし、床除算(floor division)や天井除算(ceiling division)を行うメソッドが存在しないため、有理数の床・天井を求めるには次のような複雑なワークアラウンドが必要でした。
// 従来の天井除算のワークアラウンド(x.Denom() は常に正)
func Ceil(z *big.Int, x *big.Rat) *big.Int {
    n := new(big.Int).Neg(x.Num())
    d := new(big.Int).Neg(x.Denom()) // d を負にして Div を天井除算に転用
    z.Div(n, d)
    return z
}

このトリックは Div の挙動(除数が負のとき天井除算になる)を利用したもので、意図が非常に読み取りにくい問題がありました。

提案された解決策

Int 型に Divide メソッドを追加し、既存の math/big.RoundingMode 型を使って丸めモードを指定できるようにします。また、可読性のために新しい定数エイリアスを追加します。

// Divide は整数商 q と余り r を計算します:
//   q = f(x/y)
//   r = x - y*q
// ここで f は RoundingMode で記述されます。
// z != nil の場合は z に q をセットし、r != nil の場合は余りを更新し、
// (z, r) を返します。y == 0 の場合はゼロ除算パニックが発生します。
// mode は Trunc、Floor、Round、Ceil のいずれかでなければなりません。
// 商が不要な場合は z に nil を指定できます。
// 余りが不要な場合は r に nil を指定できます。
func (z *Int) Divide(x, y, r *Int, mode RoundingMode) (*Int, *Int)
// 丸めモードを分かりやすくするための定数エイリアス
const (
    Trunc = ToZero        // T除算(Goの除算と同じ、ゼロ方向への切り捨て)
    Floor = ToNegativeInf // F除算(負の無限大方向への切り捨て)
    Round = ToNearestEven // R除算(最近接偶数丸め)
    Ceil  = ToPositiveInf // C除算(正の無限大方向への切り上げ)
)

これによって何ができるようになるか

丸め方式を明示的に指定した整数除算が、直感的な構文で書けるようになります。

コード例

// Before: 天井除算のワークアラウンド(意図が不明瞭)
n := new(big.Int).Neg(x.Num())
d := new(big.Int).Neg(x.Denom())
z.Div(n, d) // d を負にすることで天井除算を実現
// After: Divide メソッドで意図を明示
z, _ := new(big.Int).Divide(x.Num(), x.Denom(), nil, big.Ceil)
// ユースケース例: Engel展開の計算
func ToEngel(u *big.Rat) (seq []*big.Int) {
    one := big.NewRat(1, 1)
    for {
        // ⌈denom/num⌉ を天井除算で計算
        a, _ := new(big.Int).Divide(u.Denom(), u.Num(), nil, big.Ceil)
        seq = append(seq, a)
        u.SetFrac(u.Num().Mul(u.Num(), a), u.Denom())
        u.Sub(u, one)
        if u.Num().Sign() == 0 {
            break
        }
    }
    return seq
}
// ユースケース例: 浮動小数点数テーブル生成(近似計算)
// 128ビット精度で ceil(10^n * 2^m) を計算
d, _ := new(big.Int).Divide(r.Num(), r.Denom(), nil, big.Ceil)
hi, lo := new(big.Int).Divide(d, b1p64, new(big.Int), big.Ceil)

議論のハイライト

  • 最初の提案から設計が大きく進化: 当初は Rat.Floor()Rat.Ceil()*Int を返すシンプルなメソッドとして提案されたが、big.Int に直接メソッドを追加する方が汎用性が高いとの議論を経て、設計が変化した。
  • 命名論争: FloorDiv vs FloorQuo の命名について @griesemer と @magical の間で議論が展開。@griesemer はGoの /Quo(商)に対応するため FloorQuo を主張し、@magical は演算(操作)名として FloorDiv を主張した。最終的には単一の Divide メソッドに統合されることで決着。
  • 単一メソッド + 丸めモードへの統合: @aclements が「floor, ceil, truncがあるならroundも含めた4つの標準丸めモードを揃えるべき」と提案。@griesemer が Float 型の RoundingMode を流用する形で Divide メソッドへの統合を提案し、採用された。
  • 既存の RoundingMode 型の再利用: math/big には浮動小数点数用の RoundingMode 型が既に存在しており、パッケージ内の一貫性のためにこれを Int.Divide にも採用することになった。
  • zr の省略可能化: 商が不要な場合は znil、余りが不要な場合は rnil を渡せる設計が採用された。これにより用途に応じた最適な呼び出しが可能になる。

関連リンク