[インデックス 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
を、失敗時にはnil
とfalse
を返します。
技術的詳細
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
}
このメソッドの動作を詳しく解説します:
-
ゼロチェック:
len(x.a.abs) == 0
で分子がゼロかどうかを確認。ゼロの場合は逆数が定義されないため、パニックを発生させます。 -
値のコピー:
z.Set(x)
で入力値x
をz
にコピー。これにより、x
とz
が異なるインスタンスでも正しく動作します。 -
分子と分母の交換:
z.a.abs, z.b = z.b, z.a.abs
で分子の絶対値と分母を交換。コメントにあるように、符号(z.a.neg
)は変更されません。これは数学的に正しい挙動です:- 正の有理数
3/4
→4/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. メソッドの一貫性向上
Abs
やNeg
メソッドがSet
を呼ぶように変更されたことで:
- コードの重複が減少
- 自己代入チェックが自動的に適用される
- 将来の
Set
メソッドの改善が他のメソッドにも反映される