[インデックス 13153] ファイルの概要
このコミットは、Go言語の標準ライブラリ math/big
パッケージにおける Rat
型(有理数)の Denom()
メソッドの挙動を修正し、常に分母への参照を返すように変更するものです。これにより、ドキュメントの記述と実際の挙動が一致し、APIの一貫性が向上します。
コミット
commit 07612b8db012efadc2a9182160ba54702d9d04bf
Author: Robert Griesemer <gri@golang.org>
Date: Thu May 24 10:49:38 2012 -0700
math/big: make Rat.Denom() always return a reference
The documentation says so, but in the case of a normalized
integral Rat, the denominator was a new value. Changed the
internal representation to use an Int to represent the
denominator (with the sign ignored), so a reference to it
can always be returned.
Clarified documentation and added test cases.
Fixes #3521.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6237045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/07612b8db012efadc2a9182160ba54702d9d04bf
元コミット内容
math/big: make Rat.Denom() always return a reference
ドキュメントにはそのように記載されているにもかかわらず、正規化された整数値の Rat
の場合、分母は新しい値として返されていました。内部表現を Int
を使用して分母を表すように変更しました(符号は無視されます)。これにより、常にその参照を返すことができるようになります。
ドキュメントを明確にし、テストケースを追加しました。
Fixes #3521.
変更の背景
Go言語の math/big
パッケージは、任意精度(arbitrary-precision)の数値演算を提供します。その中の Rat
型は有理数を表現し、分子(Numerator)と分母(Denominator)を持ちます。
このコミットが行われる前、Rat
型の Denom()
メソッドのドキュメントには「結果は x
の分母への参照である」と明記されていました。しかし、実際の実装では、Rat
が整数値として正規化されている場合(例: 5/1 のように分母が1である場合)、Denom()
メソッドは新しい Int
オブジェクトを生成して返していました。これは、ドキュメントの約束と実際の挙動との間に不一致を生じさせていました。
この不一致は、ユーザーが Denom()
から返された Int
オブジェクトを変更しようとした際に、元の Rat
オブジェクトの分母が期待通りに更新されないという問題を引き起こす可能性がありました。特に、Rat
が内部的に分母を nat
型(符号なしの任意精度整数)として保持しており、分母が1の場合には特別な扱いをしていたことが原因でした。nat
型は Int
型とは異なり、符号情報を持たず、また Int
型のように直接参照を返すことが難しい構造になっていました。
この問題は、GoのIssue #3521として報告されており、このコミットはその問題を解決するために作成されました。
前提知識の解説
math/big
パッケージ: Go言語の標準ライブラリの一部で、任意精度の整数 (Int
)、有理数 (Rat
)、浮動小数点数 (Float
) を扱うための機能を提供します。通常のGoの組み込み数値型(int
,float64
など)では表現できない非常に大きな数値や、精度が重要な計算に用いられます。Rat
型:math/big
パッケージで定義される有理数型です。内部的には分子と分母の2つの任意精度整数で構成されます。例えば、NewRat(1, 3)
は1/3を表します。Int
型:math/big
パッケージで定義される任意精度整数型です。符号(正負)と絶対値(nat
型)を持ちます。nat
型:math/big
パッケージの内部で使われる、符号なしの任意精度整数型です。Int
型の絶対値を表現するために使われます。- 正規化 (Normalization): 有理数において、分子と分母を最大公約数で割って、互いに素な状態にすることです。例えば、2/4 は 1/2 に正規化されます。また、整数値は分母が1として表現されます(例: 5 は 5/1)。
- 参照渡しと値渡し:
- 値渡し: 関数の引数として渡された値のコピーが作成され、関数内でそのコピーが変更されても元の値には影響しません。
- 参照渡し: 関数の引数として渡された値のメモリアドレス(参照)が渡され、関数内でその参照を通じて値が変更されると、元の値も変更されます。Goでは、ポインタを使用することで参照渡しと同様の挙動を実現します。
*Int
のようにポインタ型で返される場合、それは参照を意味します。
len(nat)
が0の場合:math/big
パッケージの内部実装において、nat
型の長さが0であることは、その値が0であることを意味します。ただし、Rat
の分母の場合、len(b) == 0
は分母が1であることを意味する特別な規約がありました。これは、整数値のRat
を効率的に表現するための最適化でした。
技術的詳細
このコミットの核心は、Rat
型の内部構造と Denom()
メソッドの挙動の変更にあります。
変更前の問題点:
Rat
型は以前、以下のように定義されていました。
type Rat struct {
a Int // numerator
b nat // denominator; len(b) == 0 acts like b == 1
}
ここで、分母 b
は nat
型でした。nat
型は符号を持たないため、Denom()
メソッドが *Int
型(符号を持つ)を返すためには、nat
を Int
に変換する必要がありました。特に、Rat
が整数値(例: 5)として正規化されている場合、内部の b
は len(b) == 0
の状態であり、これは分母が1であることを意味していました。この時、Denom()
は Int{abs: nat{1}}
のように新しい Int
オブジェクトを生成して返していました。
// 変更前の Denom() の一部
func (x *Rat) Denom() *Int {
if len(x.b) == 0 {
return &Int{abs: nat{1}} // ここで新しい Int が生成される
}
return &Int{abs: x.b} // ここでも新しい Int が生成される
}
この挙動は、Denom()
が「参照を返す」というドキュメントの約束に反していました。ユーザーが r.Denom().SetInt64(3)
のように操作しても、元の r
の分母は変更されず、r
は依然として整数値のままでした。
変更後の解決策:
このコミットでは、Rat
型の内部構造を以下のように変更しました。
type Rat struct {
// To make zero values for Rat work w/o initialization,
// a zero value of b (len(b) == 0) acts like b == 1.
// a.neg determines the sign of the Rat, b.neg is ignored.
a, b Int // numerator, denominator
}
分母 b
の型が nat
から Int
に変更されました。これにより、Denom()
メソッドは x.b
のアドレスを直接返すことができるようになります。Int
型は符号を持つため、分母は常に正の値であるという要件を満たすために、Denom()
メソッド内で x.b.neg = false
と設定されます。また、分母が1の場合の内部表現(len(x.b.abs) == 0
)も引き続きサポートされますが、この場合も x.b
自体への参照が返されるようになります。
// 変更後の Denom()
func (x *Rat) Denom() *Int {
x.b.neg = false // the result is always >= 0
if len(x.b.abs) == 0 {
x.b.abs = x.b.abs.set(natOne) // materialize denominator
}
return &x.b // x.b への参照を返す
}
この変更により、Denom()
が返す *Int
は常に Rat
オブジェクトの内部的な分母の Int
フィールドへの直接の参照となります。したがって、ユーザーが r.Denom()
を介して分母を変更すると、その変更は元の r
オブジェクトに反映されるようになります。
影響範囲:
Rat
構造体の定義変更。Denom()
メソッドのロジック変更。SetFrac
,SetFrac64
,SetInt
,SetInt64
,Set
,Inv
など、Rat
の分母を操作する多くのメソッドで、nat
からInt
へのアクセス方法の変更(x.b
からx.b.abs
へ)。IsInt()
,norm()
,Cmp()
,Add()
,Sub()
,Mul()
,Quo()
,SetString()
,String()
,FloatString()
,GobEncode()
,GobDecode()
など、分母にアクセスするすべてのメソッドで、x.b
の代わりにx.b.abs
を使用するように修正。- テストケースの追加 (
TestIssue3521
) により、新しい挙動が正しく機能することを確認。特に、Denom()
が返すInt
への変更が元のRat
に反映されることを検証。
この変更は、APIの契約(ドキュメント)と実装の整合性を保つ上で非常に重要であり、math/big
パッケージの信頼性と使いやすさを向上させます。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は、src/pkg/math/big/rat.go
と src/pkg/math/big/rat_test.go
に集中しています。
src/pkg/math/big/rat.go
:
-
Rat
構造体の定義変更:--- a/src/pkg/math/big/rat.go +++ b/src/pkg/math/big/rat.go @@ -16,8 +16,10 @@ import ( // A Rat represents a quotient a/b of arbitrary precision. // The zero value for a Rat represents the value 0. type Rat struct { - a Int - b nat // len(b) == 0 acts like b == 1 + // To make zero values for Rat work w/o initialization, + // a zero value of b (len(b) == 0) acts like b == 1. + // a.neg determines the sign of the Rat, b.neg is ignored. + a, b Int }
分母
b
の型がnat
からInt
に変更されました。 -
Denom()
メソッドの変更:--- a/src/pkg/math/big/rat.go +++ b/src/pkg/math/big/rat.go @@ -121,24 +123,26 @@ func (x *Rat) Sign() int { // Denom returns the denominator of x; it is always > 0. // The result is a reference to x's denominator; it // may change if a new value is assigned to x. +// may change if a new value is assigned to x, and vice versa. func (x *Rat) Denom() *Int { - if len(x.b) == 0 { - return &Int{abs: nat{1}} + x.b.neg = false // the result is always >= 0 + if len(x.b.abs) == 0 { + x.b.abs = x.b.abs.set(natOne) // materialize denominator } - return &Int{abs: x.b} + return &x.b }
x.b
がInt
型になったため、直接&x.b
を返すように変更されました。また、分母は常に正であるためx.b.neg = false
が追加されました。 -
分母
b
へのアクセス変更:Rat
構造体のb
がnat
からInt
に変更されたため、b
の絶対値にアクセスする際にはb.abs
を使用するように、関連するすべてのメソッドが修正されました。SetFrac
,SetFrac64
,SetInt
,SetInt64
,Set
,Inv
IsInt
norm
(正規化処理)Cmp
,Add
,Sub
,Mul
,Quo
(演算メソッド)SetString
,String
,FloatString
(文字列変換メソッド)GobEncode
,GobDecode
(gobエンコーディング/デコーディング)
src/pkg/math/big/rat_test.go
:
TestIssue3521
関数の追加: この新しいテストケースは、Denom()
メソッドの新しい挙動、特に返されたInt
への変更が元のRat
オブジェクトに反映されることを検証します。- ゼロ値の
Rat
の分母が1であることを確認。 x.Denom().Set(...)
を使って分母を変更した際に、x
自体の値が変化することを確認。x.Num()
とx.Denom()
が返す参照を介してRat
の分子と分母を変更し、その変更がRat
オブジェクトに反映されることを確認。
- ゼロ値の
コアとなるコードの解説
Rat
構造体の変更:
type Rat struct {
a, b Int
}
最も根本的な変更は、Rat
構造体の分母 b
の型が nat
から Int
になったことです。これにより、b
はそれ自体が符号を持つ任意精度整数となり、Denom()
メソッドが *Int
を返す際に、新しいオブジェクトを生成することなく、b
フィールドへの直接のポインタを返すことが可能になりました。
Denom()
メソッドの変更:
func (x *Rat) Denom() *Int {
x.b.neg = false // the result is always >= 0
if len(x.b.abs) == 0 {
x.b.abs = x.b.abs.set(natOne) // materialize denominator
}
return &x.b
}
x.b.neg = false
: 有理数の分母は常に正であるという数学的な定義に基づき、Int
型のb
の符号ビットを強制的にfalse
(正)に設定します。これにより、Denom()
が返すInt
は常に正の値となります。if len(x.b.abs) == 0 { x.b.abs = x.b.abs.set(natOne) }
: これは、Rat
が整数値として正規化されている場合(内部的に分母が1として扱われる場合)の処理です。以前はlen(x.b) == 0
でしたが、b
がInt
型になったため、その絶対値であるx.b.abs
の長さが0であるかをチェックします。もしそうであれば、x.b.abs
をnatOne
(値が1のnat
)に設定し、分母が明示的に1であることを保証します。この「materialize denominator(分母を実体化する)」というコメントは、内部的な最適化表現から、実際に値が1のInt
として分母を準備する意味合いです。return &x.b
: これが最も重要な変更点です。x.b
はRat
構造体の一部であるInt
型のフィールドであり、そのアドレスを直接返します。これにより、呼び出し元は返された*Int
ポインタを介してRat
オブジェクトの実際の分母を直接操作できるようになります。
その他のメソッドの変更:
Rat
構造体の b
フィールドが nat
から Int
に変更されたため、b
の絶対値にアクセスするすべての箇所で x.b
を x.b.abs
に変更する必要がありました。例えば、gcd
関数を呼び出す際や、cmp
、div
、mul
などの nat
メソッドを呼び出す際に、x.b
ではなく x.b.abs
が引数として渡されるようになっています。これにより、新しい構造体定義との整合性が保たれています。
これらの変更により、Rat.Denom()
はドキュメントの約束通り、常に内部の分母 Int
フィールドへの参照を返すようになり、APIの予測可能性と一貫性が大幅に向上しました。
関連リンク
- Go言語
math/big
パッケージのドキュメント: https://pkg.go.dev/math/big - Go言語のコードレビューシステム (Gerrit) での変更セット: https://golang.org/cl/6237045
- Go Issue #3521:
math/big: Rat.Denom() should return a reference to the actual denominator
(このコミットが解決した問題): https://github.com/golang/go/issues/3521
参考にした情報源リンク
- 上記の関連リンクに記載されているGo言語の公式ドキュメント、Issueトラッカー、およびGerritの変更セット。
- Go言語のソースコード(
src/pkg/math/big/rat.go
およびsrc/pkg/math/big/rat_test.go
)。 - 任意精度算術に関する一般的な知識。
- Go言語におけるポインタと参照の概念。