[インデックス 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メソッドの改善が他のメソッドにも反映される