#76821accepted
math/big: add Int.Divide method with rounding modes
ステータス変更: likely_accept accepted
要約
AIによる要約であり、誤りを含む場合があります。
概要
math/big パッケージの Int 型に、丸めモードを指定できる統一的な整数除算メソッド Divide を追加するプロポーザルです。床除算・天井除算・切り捨て除算・最近接偶数丸め除算を一つのAPIで扱えるようにします。
ステータス変更
likely_accept → accepted
2026年3月11日に "likely accept" となってから1週間、コンセンサスに変化がなかったため、2026年3月18日に @aclements がproposal review groupを代表して正式に "accepted" を宣言しました。
技術的背景
現状の問題点
math/big の Int 型には既に複数の除算メソッドが存在しますが、丸め方式が統一されていません。
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に直接メソッドを追加する方が汎用性が高いとの議論を経て、設計が変化した。 - 命名論争:
FloorDivvsFloorQuoの命名について @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にも採用することになった。 zとrの省略可能化: 商が不要な場合はzにnil、余りが不要な場合はrにnilを渡せる設計が採用された。これにより用途に応じた最適な呼び出しが可能になる。