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

[インデックス 10072] ファイルの概要

コミット

  • コミットハッシュ: 4854bd9cedcdf575fe84a7b39b528744f26859ce
  • 作成者: Robert Griesemer gri@golang.org
  • 日時: 2011年10月21日 13:26:00 -0700

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/4854bd9cedcdf575fe84a7b39b528744f26859ce

元コミット内容

big: implemented Rat.Inv

Also:
- changed semantics of return values for [Int|Rat].SetString
  if an error occured (returned value is nil); will expose
  hidden errors where return values are not checked
- added more tests
- various cleanups throughout

Fixes #2384.

変更の背景

このコミットは、Go言語のmath/bigパッケージにおける重要な機能追加と改善を行ったものです。主に以下の3つの背景があります:

1. 有理数の逆数計算機能の必要性

math/bigパッケージには任意精度の有理数を扱うRat型がありましたが、逆数(reciprocal)を計算する専用メソッドが欠けていました。数学的計算において、有理数a/bの逆数b/aを求める操作は基本的な演算の一つであり、この機能の実装が求められていました(Issue #2384)。

2. エラーハンドリングの改善

従来のSetStringメソッドは、エラーが発生した場合でも変更されたレシーバのポインタを返していました。これにより、エラーチェックを怠った場合に、不正な値が使用される可能性がありました。このコミットでは、エラー時にnilを返すように変更することで、潜在的なバグを顕在化させやすくしています。

3. コードの整理と品質向上

2011年当時、Go言語はまだ初期段階(Go 1.0は2012年3月リリース)であり、標準ライブラリの品質向上が継続的に行われていました。このコミットでは、テストの追加や既存コードのクリーンアップも含まれています。

前提知識の解説

math/bigパッケージ

math/bigパッケージは、Go言語において任意精度の整数(Int)、有理数(Rat)、浮動小数点数(Float)を扱うためのパッケージです。通常の数値型では表現できない大きな数値や、精度を失わない計算が必要な場合に使用されます。

Rat型の構造

type Rat struct {
    a Int  // 分子(numerator)
    b nat  // 分母(denominator)、常に正の値
}

Rat型は有理数を表現し、内部的に分子と分母を持ちます。ゼロ値0/0は正当なRatではありません。

メソッドレシーバのパターン

Go言語のmath/bigパッケージでは、計算結果を格納するレシーバを使用するパターンが採用されています:

func (z *Rat) Add(x, y *Rat) *Rat

この形式では、zに結果が格納され、同じzが返されます。これにより、メソッドチェーンが可能になります。

SetStringメソッド

SetStringメソッドは文字列から数値を解析して設定するメソッドです。成功時には設定された値とtrueを、失敗時にはnilfalseを返します。

技術的詳細

1. Rat.Invメソッドの実装

新しく追加されたInvメソッドは、有理数の逆数を計算します:

  • 入力:有理数 x = a/b
  • 出力:逆数 1/x = b/a
  • ゼロ除算の場合はパニックを発生させます

実装の核心は、分子と分母を交換することです:

z.a.abs, z.b = z.b, z.a.abs // 符号は変更しない

2. SetStringのエラーハンドリング改善

Int.SetString

  • エラー時の返り値を(z, false)から(nil, false)に変更
  • scanメソッドも同様にエラー時はnilを返すように修正
  • 文字列全体が消費されたかどうかを確認(err == os.EOF

Rat.SetString

  • 空文字列、不正な分数表記、不正な浮動小数点表記の場合にnilを返す
  • 各段階でのエラーチェックを強化

3. メソッドの再実装と最適化

Set/Abs/Negメソッドの改善

  • 自己代入のチェック(if z != x)を追加
  • 重複コードを削減し、Setメソッドを活用
  • ゼロの符号処理を一貫させる

4. テストの拡充

  • TestRatInv:逆数計算のテスト追加
  • TestRatNeg:符号反転のテスト追加
  • 既存テストのエラーハンドリング改善(nilチェックの追加)

コアとなるコードの変更箇所

1. Rat.Invメソッドの追加(src/pkg/big/rat.go)

// Inv sets z to 1/x and returns z.
func (z *Rat) Inv(x *Rat) *Rat {
    if len(x.a.abs) == 0 {
        panic("division by zero")
    }
    z.Set(x)
    z.a.abs, z.b = z.b, z.a.abs // sign doesn't change
    return z
}

2. Int.SetStringのエラーハンドリング(src/pkg/big/int.go)

func (z *Int) SetString(s string, base int) (*Int, bool) {
    r := strings.NewReader(s)
    _, _, err := z.scan(r, base)
    if err != nil {
        return nil, false  // 変更前:return z, false
    }
    _, _, err = r.ReadRune()
    if err != os.EOF {
        return nil, false  // 変更前:return z, false
    }
    return z, true
}

3. Int.Setメソッドの自己代入対応(src/pkg/big/int.go)

func (z *Int) Set(x *Int) *Int {
    if z != x {  // 自己代入チェックを追加
        z.abs = z.abs.set(x.abs)
        z.neg = x.neg
    }
    return z
}

コアとなるコードの解説

1. Invメソッドの実装詳細

func (z *Rat) Inv(x *Rat) *Rat {
    if len(x.a.abs) == 0 {
        panic("division by zero")
    }
    z.Set(x)
    z.a.abs, z.b = z.b, z.a.abs // sign doesn't change
    return z
}

このメソッドの動作を詳しく解説します:

  1. ゼロチェックlen(x.a.abs) == 0で分子がゼロかどうかを確認。ゼロの場合は逆数が定義されないため、パニックを発生させます。

  2. 値のコピーz.Set(x)で入力値xzにコピー。これにより、xzが異なるインスタンスでも正しく動作します。

  3. 分子と分母の交換z.a.abs, z.b = z.b, z.a.absで分子の絶対値と分母を交換。コメントにあるように、符号(z.a.neg)は変更されません。これは数学的に正しい挙動です:

    • 正の有理数 3/44/3(正のまま)
    • 負の有理数 -3/4-4/3(負のまま)

2. SetStringのnilリターン戦略

エラー時にnilを返すことで、以下のような潜在的バグを防げます:

// 悪い例(変更前)
x, ok := new(Int).SetString("invalid", 10)
// okをチェックし忘れても、xは非nilなので使用できてしまう
y := new(Int).Add(x, one) // 不正な値で計算が続行される

// 良い例(変更後)
x, ok := new(Int).SetString("invalid", 10)
// okをチェックし忘れた場合、xはnilなのでパニックが発生
y := new(Int).Add(x, one) // nilポインタ参照でパニック

3. 自己代入の最適化

if z != xのチェックにより、以下のようなケースで無駄なコピーを避けられます:

var x Int
x.SetInt64(42)
x.Set(&x) // 自己代入:チェックにより何もしない

4. メソッドの一貫性向上

AbsNegメソッドがSetを呼ぶように変更されたことで:

  • コードの重複が減少
  • 自己代入チェックが自動的に適用される
  • 将来のSetメソッドの改善が他のメソッドにも反映される

関連リンク

参考にした情報源リンク