[インデックス 14583] ファイルの概要
このコミットは、Go言語の標準ライブラリ math/rand
パッケージに対して、新しいexample_test.go
ファイルを追加するものです。このファイルは、math/rand
パッケージの様々な乱数生成メソッドの使用例を示すと同時に、特定のシード値を用いた場合の乱数生成結果が将来の変更によって変わらないことを保証するための回帰テストとしても機能します。これにより、乱数生成の再現性と安定性が確保されます。
コミット
commit b99161e41f6a89911a6c438da738aa2154622c07
Author: Russ Cox <rsc@golang.org>
Date: Fri Dec 7 11:58:59 2012 -0500
math/rand: add example / regression test
This makes sure the outputs do not change for a fixed seed.
See also https://golang.org/cl/6905049.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/6907048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b99161e41f6a89911a6c438da738aa2154622c07
元コミット内容
math/rand
: add example / regression test
This makes sure the outputs do not change for a fixed seed. See also https://golang.org/cl/6905049.
変更の背景
この変更の主な背景は、math/rand
パッケージの乱数生成器の出力が、固定されたシード値に対して常に同じであることを保証することです。乱数生成器は、通常、シード値に基づいて一連の擬似乱数を生成します。シード値が同じであれば、生成される乱数のシーケンスも同じになるべきです。これは、テストの再現性、シミュレーション、暗号化など、多くのアプリケーションで非常に重要です。
もし乱数生成アルゴリズムが変更された場合、その変更が意図せず乱数シーケンスに影響を与え、既存のテストやアプリケーションの動作を壊す可能性があります。このコミットで追加されたexample_test.go
は、Example
関数として実装されており、Goのテストフレームワークの機能を利用して、ドキュメントの例として表示されるだけでなく、テストスイートの一部としても実行されます。これにより、math/rand
パッケージの将来の変更が、固定シードでの出力に影響を与えないことを継続的に検証する回帰テストとして機能します。
コミットメッセージにある https://golang.org/cl/6905049
は、この変更に関連する別のコードレビュー(Change List)を示唆しており、おそらく乱数生成器の内部的な変更や改善に関する議論があったことを示しています。このコミットは、その変更が外部に与える影響を検証するためのものです。
前提知識の解説
擬似乱数生成器 (PRNG) とシード
コンピュータで生成される乱数は、実際には「擬似乱数」と呼ばれます。これは、完全にランダムではなく、特定のアルゴリズムに基づいて生成されるためです。このアルゴリズムは、初期値である「シード(seed)」から開始し、一連の計算によって乱数のようなシーケンスを生成します。
- シード: 乱数生成器の初期状態を決定する値です。同じシード値を使用すると、常に同じ乱数シーケンスが生成されます。これは、テストの再現性や、特定のシミュレーション結果を再現したい場合に非常に重要です。
- 再現性: 固定シードを使用することで、プログラムを何度実行しても同じ乱数シーケンスが得られることを保証できます。これは、バグの特定や、アルゴリズムの動作検証に不可欠です。
Go言語の math/rand
パッケージ
Go言語の標準ライブラリ math/rand
パッケージは、擬似乱数を生成するための機能を提供します。
rand.Source
インターフェース: 乱数生成器のシードと状態を管理するためのインターフェースです。rand.NewSource(seed int64)
関数は、int64
型のシード値を受け取り、このインターフェースを実装したオブジェクトを返します。rand.Rand
型:rand.Source
をラップし、様々な型の乱数を生成するためのメソッド(Intn
,Float64
,Perm
など)を提供する構造体です。rand.New(source rand.Source)
関数を使って、特定のシードを持つ乱数生成器のインスタンスを作成できます。- グローバル乱数生成器:
math/rand
パッケージは、パッケージレベルのグローバルな乱数生成器も提供しています(例:rand.Intn
,rand.Float64
)。これらはデフォルトで固定のシード(通常は1)で初期化されるため、プログラムを再起動するたびに同じシーケンスを生成します。異なるシーケンスが必要な場合は、rand.Seed(time.Now().UnixNano())
のように、実行ごとに異なるシード(例: 現在時刻)で明示的に初期化する必要があります。
Go言語の Example
関数
Goのテストフレームワークには、Example
という特別な種類のテスト関数があります。
- ドキュメント生成:
Example
関数は、go doc
コマンドやGoの公式ドキュメントサイトで、コードの実行例として表示されます。これにより、ライブラリの使い方が視覚的に分かりやすくなります。 - テストとしての実行:
Example
関数は、通常のテスト関数(TestXxx
)と同様に、go test
コマンドによって実行されます。Example
関数内にコメントとして記述されたOutput:
ブロックと、実際の関数の標準出力が比較され、一致しない場合はテストが失敗します。これにより、例が常に正しく動作することを保証できます。
text/tabwriter
パッケージ
text/tabwriter
パッケージは、テキストを整形して、タブ区切りのデータをきれいに揃えて出力するためのユーティリティを提供します。このコミットのコードでは、乱数生成結果を整形して表示するために使用されています。
技術的詳細
このコミットで追加されたexample_test.go
ファイルは、math/rand
パッケージのRand
型が提供する主要な乱数生成メソッドの動作を網羅的に示しています。
-
シードの固定:
r := rand.New(rand.NewSource(99))
ここでは、
rand.NewSource(99)
を使用して、シード値99
で初期化された乱数生成器r
を作成しています。これにより、このExample
関数が実行されるたびに、r
から生成される乱数のシーケンスは常に同じになります。これが回帰テストとしての機能の核心です。 -
text/tabwriter
の使用:w := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0) defer w.Flush() show := func(name string, v1, v2, v3 interface{}) { fmt.Fprintf(w, "%s\t%v\t%v\t%v\n", name, v1, v2, v3) }
tabwriter.NewWriter
を使用して、標準出力に整形されたテキストを書き込むためのライターを作成しています。show
ヘルパー関数は、指定された名前と3つの乱数値をタブ区切りで出力し、tabwriter
が自動的に列を揃えます。defer w.Flush()
は、関数が終了する前にバッファリングされた出力を確実に書き出すために使用されます。 -
様々な乱数生成メソッドのデモンストレーションと検証: コードは、
rand.Rand
型の以下のメソッドを呼び出し、その結果をshow
関数で出力しています。Float32()
,Float64()
: 0.0以上1.0未満の浮動小数点数を生成します。ExpFloat64()
: 平均1.0で指数関数的に減衰する浮動小数点数を生成します。NormFloat64()
: 平均0.0、標準偏差1.0の正規分布に従う浮動小数点数を生成します(ガウス分布)。Int31()
,Int63()
,Uint32()
: それぞれ31ビット、63ビット、32ビットの符号なし整数を生成します。Uint32
はInt63
の戻り値をuint32
にキャストして使用しています。Intn(n int)
,Int31n(n int32)
,Int63n(n int64)
: 0以上n
未満の整数を生成します。これらのメソッドは、r.Int()%n
のように単純な剰余演算を使用するよりも、より均一な分布を保証します。Perm(n int)
:[0, n)
の範囲の整数をランダムに並べ替えたスライスを生成します。
-
Output:
コメントブロック:Example
関数の末尾には、期待される出力が// Output:
コメントブロックとして記述されています。go test
コマンドがこのExample
関数を実行すると、実際の出力がこのブロックの内容と比較されます。もし一致しない場合、テストは失敗し、乱数生成器の動作が意図せず変更されたことを開発者に通知します。
この構造により、math/rand
パッケージの機能が適切に動作し続けることが保証され、同時に開発者やユーザーがこれらのメソッドの典型的な使用方法を理解するための明確な例が提供されます。
コアとなるコードの変更箇所
diff --git a/src/pkg/math/rand/example_test.go b/src/pkg/math/rand/example_test.go
new file mode 100644
index 0000000000..997385c016
--- /dev/null
+++ b/src/pkg/math/rand/example_test.go
@@ -0,0 +1,69 @@
+// Copyright 2012 The Go Authors. All rights reserved.\n+// Use of this source code is governed by a BSD-style\n+// license can be found in the LICENSE file.\n+\n+package rand_test\n+\n+import (\n+\t\"fmt\"\n+\t\"math/rand\"\n+\t\"os\"\n+\t\"text/tabwriter\"\n+)\n+\n+// This test serves as an example but also makes sure we don\'t change\n+// the output of the random number generator when given a fixed seed.\n+\n+// This example shows the use of each of the methods on a *Rand.\n+// The use of the global functions is the same, without the receiver.\n+func Example() {\n+\t// Create and seed the generator.\n+\t// Typically a non-fixed seed should be used, such as time.Now().UnixNano().\n+\t// Using a fixed seed will produce the same output on every run.\n+\tr := rand.New(rand.NewSource(99))\n+\n+\t// The tabwriter here helps us generate aligned output.\n+\tw := tabwriter.NewWriter(os.Stdout, 1, 1, 1, \' \', 0)\n+\tdefer w.Flush()\n+\tshow := func(name string, v1, v2, v3 interface{}) {\n+\t\tfmt.Fprintf(w, \"%s\\t%v\\t%v\\t%v\\n\", name, v1, v2, v3)\n+\t}\n+\n+\t// Float32 and Float64 values are in [0, 1).\n+\tshow(\"Float32\", r.Float32(), r.Float32(), r.Float32())\n+\tshow(\"Float64\", r.Float64(), r.Float64(), r.Float64())\n+\n+\t// ExpFloat64 values have an average of 1 but decay exponentially.\n+\tshow(\"ExpFloat64\", r.ExpFloat64(), r.ExpFloat64(), r.ExpFloat64())\n+\n+\t// NormFloat64 values have an average of 0 and a standard deviation of 1.\n+\tshow(\"NormFloat64\", r.NormFloat64(), r.NormFloat64(), r.NormFloat64())\n+\n+\t// Int31, Int63, and Uint32 generate values of the given width.\n+\t// The Int method (not shown) is like either Int31 or Int64\n+\t// depending on the size of \'int\'.\n+\tshow(\"Int31\", r.Int31(), r.Int31(), r.Int31())\n+\tshow(\"Int63\", r.Int63(), r.Int63(), r.Int63())\n+\tshow(\"Uint32\", r.Int63(), r.Int63(), r.Int63())\n+\n+\t// Intn, Int31n, and Int63n limit their output to be < n.\n+\t// They do so more carefully than using r.Int()%n.\n+\tshow(\"Intn(10)\", r.Intn(10), r.Intn(10), r.Intn(10))\n+\tshow(\"Int31n(10)\", r.Int31n(10), r.Int31n(10), r.Int31n(10))\n+\tshow(\"Int63n(10)\", r.Int63n(10), r.Int63n(10), r.Int63n(10))\n+\n+\t// Perm generates a random permutation of the numbers [0, n).\n+\tshow(\"Perm\", r.Perm(5), r.Perm(5), r.Perm(5))\n+\t// Output:\n+\t// Float32 0.2635776 0.6358173 0.6718283\n+\t// Float64 0.628605430454327 0.4504798828572669 0.9562755949377957\n+\t// ExpFloat64 0.3362240648200941 1.4256072328483647 0.24354758816173044\n+\t// NormFloat64 0.17233959114940064 1.577014951434847 0.04259129641113857\n+\t// Int31 1501292890 1486668269 182840835\n+\t// Int63 3546343826724305832 5724354148158589552 5239846799706671610\n+\t// Uint32 5927547564735367388 637072299495207830 4128311955958246186\n+\t// Intn(10) 1 2 5\n+\t// Int31n(10) 4 7 8\n+\t// Int63n(10) 7 6 3\n+\t// Perm [1 4 2 3 0] [4 2 1 3 0] [1 2 4 0 3]\n```
## コアとなるコードの解説
追加された `src/pkg/math/rand/example_test.go` ファイルは、`rand_test` パッケージに属しています。これは、`math/rand` パッケージの内部実装に依存せず、公開されたAPIのみを使用してテストを行うためのGoの慣習です。
ファイル内の `Example()` 関数は、以下の主要な部分で構成されています。
1. **パッケージのインポート**:
`fmt`, `math/rand`, `os`, `text/tabwriter` の各パッケージがインポートされています。これらは、出力のフォーマット、乱数生成、標準出力への書き込みにそれぞれ使用されます。
2. **`Example()` 関数の定義**:
この関数は、Goのテストフレームワークによって特別な方法で扱われます。関数名の `Example` が、これがドキュメントの例であり、かつテストとして実行されることを示します。
3. **乱数生成器の初期化**:
```go
r := rand.New(rand.NewSource(99))
```
`rand.NewSource(99)` を使用して、固定シード `99` で新しい乱数ソースを作成し、それを `rand.New()` に渡して `*rand.Rand` 型の乱数生成器 `r` をインスタンス化しています。これにより、この例を実行するたびに同じ乱数シーケンスが生成されることが保証されます。コメントで `time.Now().UnixNano()` のような非固定シードが通常推奨されることが示されていますが、この場合は回帰テストの目的のために固定シードが意図的に使用されています。
4. **`tabwriter` の設定とヘルパー関数 `show`**:
```go
w := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0)
defer w.Flush()
show := func(name string, v1, v2, v3 interface{}) {
fmt.Fprintf(w, "%s\t%v\t%v\t%v\n", name, v1, v2, v3)
}
```
`tabwriter.NewWriter` は、タブ区切りのテキストを整形して出力するためのライターを作成します。引数はそれぞれ、出力先 (`os.Stdout`)、最小幅、タブストップ幅、パディング、パディング文字、フラグです。`defer w.Flush()` は、関数が終了する際に`tabwriter`のバッファをフラッシュし、すべての出力が書き込まれることを保証します。
`show` 関数はクロージャとして定義されており、乱数メソッドの名前と、そのメソッドから生成された3つの乱数値を`tabwriter`に書き込みます。`\t` (タブ) を使用することで、`tabwriter` が自動的に列を揃えてくれます。
5. **各種乱数生成メソッドの呼び出しと出力**:
`r` インスタンスの様々な乱数生成メソッドが呼び出され、その結果が `show` 関数を通じて整形されて出力されます。これには、浮動小数点数 (`Float32`, `Float64`, `ExpFloat64`, `NormFloat64`)、整数 (`Int31`, `Int63`, `Uint32`)、範囲指定された整数 (`Intn`, `Int31n`, `Int63n`)、そして順列 (`Perm`) が含まれます。
6. **期待される出力 (`// Output:`)**:
```go
// Output:
// Float32 0.2635776 0.6358173 0.6718283
// ... (以下略)
```
このコメントブロックは、`Example()` 関数が標準出力に書き出すべき正確な内容を定義しています。`go test` コマンドがこのファイルを処理する際、`Example()` 関数の実際の出力がこの `Output:` ブロックの内容と一字一句比較されます。もし両者が一致しない場合、テストは失敗し、`math/rand` パッケージの乱数生成アルゴリズムが意図せず変更されたことを示します。これにより、乱数生成の再現性が保証される回帰テストとして機能します。
このコードは、`math/rand` パッケージの包括的な使用例を提供すると同時に、その安定性を保証するための重要なテストケースとしての役割も果たしています。
## 関連リンク
* Go Change List 6905049: [https://golang.org/cl/6905049](https://golang.org/cl/6905049)
* Go Change List 6907048: [https://golang.org/cl/6907048](https://golang.org/cl/6907048)
## 参考にした情報源リンク
* Go の `math/rand` パッケージのドキュメント: [https://pkg.go.dev/math/rand](https://pkg.go.dev/math/rand)
* Go の `testing` パッケージのドキュメント (Example 関数について): [https://pkg.go.dev/testing](https://pkg.go.dev/testing)
* Go の `text/tabwriter` パッケージのドキュメント: [https://pkg.go.dev/text/tabwriter](https://pkg.go.dev/text/tabwriter)
* Go の Example テストについて (公式ブログなど): (Web検索で得られた情報源があればここに追記)
* A Tour of Go - Example functions: [https://go.dev/tour/moretypes/23](https://go.dev/tour/moretypes/23)
* Go by Example: Testing: [https://gobyexample.com/testing](https://gobyexample.com/testing) (Example関数の直接的な説明ではないが、テストの文脈で関連)