Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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
}

ここで、分母 bnat 型でした。nat 型は符号を持たないため、Denom() メソッドが *Int 型(符号を持つ)を返すためには、natInt に変換する必要がありました。特に、Rat が整数値(例: 5)として正規化されている場合、内部の blen(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.gosrc/pkg/math/big/rat_test.go に集中しています。

src/pkg/math/big/rat.go:

  1. 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 に変更されました。

  2. 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.bInt 型になったため、直接 &x.b を返すように変更されました。また、分母は常に正であるため x.b.neg = false が追加されました。

  3. 分母 b へのアクセス変更: Rat 構造体の bnat から 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:

  1. 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 でしたが、bInt 型になったため、その絶対値である x.b.abs の長さが0であるかをチェックします。もしそうであれば、x.b.absnatOne(値が1の nat)に設定し、分母が明示的に1であることを保証します。この「materialize denominator(分母を実体化する)」というコメントは、内部的な最適化表現から、実際に値が1の Int として分母を準備する意味合いです。
  • return &x.b: これが最も重要な変更点です。x.bRat 構造体の一部である Int 型のフィールドであり、そのアドレスを直接返します。これにより、呼び出し元は返された *Int ポインタを介して Rat オブジェクトの実際の分母を直接操作できるようになります。

その他のメソッドの変更:

Rat 構造体の b フィールドが nat から Int に変更されたため、b の絶対値にアクセスするすべての箇所で x.bx.b.abs に変更する必要がありました。例えば、gcd 関数を呼び出す際や、cmpdivmul などの nat メソッドを呼び出す際に、x.b ではなく x.b.abs が引数として渡されるようになっています。これにより、新しい構造体定義との整合性が保たれています。

これらの変更により、Rat.Denom() はドキュメントの約束通り、常に内部の分母 Int フィールドへの参照を返すようになり、APIの予測可能性と一貫性が大幅に向上しました。

関連リンク

参考にした情報源リンク

  • 上記の関連リンクに記載されているGo言語の公式ドキュメント、Issueトラッカー、およびGerritの変更セット。
  • Go言語のソースコード(src/pkg/math/big/rat.go および src/pkg/math/big/rat_test.go)。
  • 任意精度算術に関する一般的な知識。
  • Go言語におけるポインタと参照の概念。