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

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

このコミットは、Go言語の標準ライブラリである math/rand パッケージにおいて、グローバルな擬似乱数生成器の初期シード値に関するドキュメントを改善するものです。具体的には、Seed 関数が明示的に呼び出されない場合に、グローバルな乱数生成器が Seed(1) でシードされたかのように振る舞うという、既存のデフォルト動作を明確に記述するコメントが追加されました。これにより、開発者が math/rand パッケージの挙動をより正確に理解し、予期せぬ決定論的な乱数シーケンスに遭遇するのを防ぐことを目的としています。

コミット

commit 5163e7aa2724c0c81feea56f7b243b122a1d3bdf
Author: Scott Lawrence <bytbox@gmail.com>
Date:   Mon Jan 16 18:13:34 2012 -0500

    math/rand: document default initial seed for global generator
    
    Fixes #2044.
    
    R=golang-dev
    CC=golang-dev
    https://golang.org/cl/5541056

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

https://github.com/golang/go/commit/5163e7aa2724c0c81feea56f7b243b122a1d3bdf

元コミット内容

math/rand: document default initial seed for global generator

Fixes #2044.

R=golang-dev
CC=golang-dev
https://golang.org/cl/5541056

変更の背景

Go言語の math/rand パッケージは、擬似乱数生成機能を提供します。このパッケージには、プログラム全体で共有されるグローバルな乱数生成器が存在します。しかし、このグローバルな乱数生成器が、Seed 関数によって明示的にシードされない場合にどのような初期状態になるかについて、以前は公式ドキュメントに明確な記述がありませんでした。

多くのプログラミング言語の乱数ライブラリでは、シードが指定されない場合にシステム時刻などを用いて非決定論的に初期化されることが一般的です。そのため、Goの math/rand パッケージも同様の挙動を期待する開発者がいる一方で、実際には特定の固定値(この場合は 1)でシードされるという決定論的な挙動を示していました。この未文書化の挙動は、特にテストの再現性や、乱数に依存するアプリケーションのデバッグにおいて、開発者を混乱させる可能性がありました。

このコミットは、この曖昧さを解消し、グローバルな乱数生成器のデフォルトの初期シード値が 1 であるという既存の動作を明示的にドキュメント化することで、開発者が math/rand パッケージをより安全かつ意図通りに利用できるようにすることを目的としています。コミットメッセージにある Fixes #2044 は、このドキュメントの不足がGoのIssueトラッカーで報告され、その解決策としてこの変更が提案されたことを示唆しています。

前提知識の解説

擬似乱数生成器 (PRNG: Pseudo-Random Number Generator)

コンピュータは本質的に決定論的なマシンであるため、真のランダムな数値を生成することは困難です。そこで、特定のアルゴリズムと初期値(シード)を用いて、統計的にはランダムに見える数列を生成する「擬似乱数生成器」が用いられます。PRNGは、同じシード値を与えれば常に同じ数列を生成するという決定論的な特性を持ちます。これは、テストの再現性やシミュレーションなど、特定の状況下で非常に有用です。

シード (Seed)

シードとは、擬似乱数生成器の初期状態を決定する数値のことです。PRNGはシード値から計算を開始し、その後のすべての擬似乱数を生成します。したがって、同じシード値を使用すると、常に同じ擬似乱数のシーケンスが生成されます。

  • 決定論的挙動: シードを固定することで、プログラムの実行ごとに同じ乱数シーケンスが得られます。これはデバッグやテストの再現性確保に不可欠です。
  • 非決定論的挙動: 一般的なアプリケーションでは、実行ごとに異なる乱数シーケンスが必要とされるため、システム時刻など、実行ごとに変化する値をシードとして使用することが多いです。

Goの math/rand パッケージ

Go言語の math/rand パッケージは、擬似乱数生成機能を提供します。このパッケージには、以下の主要な概念があります。

  • グローバルな乱数生成器: math/rand パッケージは、パッケージレベルで利用できるグローバルな乱数生成器を持っています。rand.Int(), rand.Float64() などの関数は、このグローバルな生成器を使用します。
  • Source インターフェース: 乱数生成のアルゴリズムを定義するインターフェースです。
  • Rand 構造体: Source インターフェースをラップし、乱数生成のための便利なメソッド(Intn, Float64 など)を提供します。rand.New(source) を使って、独自のシードを持つ独立した乱数生成器を作成できます。
  • Seed 関数: グローバルな乱数生成器のシード値を設定するために使用されます。

このコミットの時点(Go 1.20より前)では、math/rand のグローバルな乱数生成器は、Seed 関数が明示的に呼び出されない場合、内部的に NewSource(1) を使用してシード 1 で初期化されていました。これは、プログラムの実行ごとに同じ乱数シーケンスが生成されることを意味します。Go 1.20以降では、このデフォルトの挙動が変更され、よりランダムなシードが自動的に使用されるようになりましたが、このコミットはそれ以前のバージョンの挙動に関するドキュメントの改善です。

技術的詳細

このコミットは、Go言語の src/pkg/math/rand/rand.go ファイル内の Seed 関数のコメントを修正することで、math/rand パッケージのグローバルな乱数生成器のデフォルトの初期化挙動を明確にしています。

math/rand パッケージの内部では、globalRand という名前の Rand 型の変数がグローバルな乱数生成器として定義されています。この globalRand は、以下のように初期化されます。

var globalRand = New(&lockedSource{src: NewSource(1)})

このコードは、globalRandNewSource(1)、つまりシード値 1 を持つソースで初期化されることを示しています。これは、Seed 関数が明示的に呼び出されない限り、グローバルな乱数生成器は常にシード 1 から開始し、結果として常に同じ擬似乱数シーケンスを生成することを意味します。

Seed 関数は、この globalRand のシード値を変更するために提供されています。

func Seed(seed int64) { globalRand.Seed(seed) }

このコミット以前の Seed 関数のコメントは、単に「指定されたシード値を使用して、生成器を決定論的な状態に初期化する」とだけ記述されていました。しかし、この記述では、Seed が一度も呼び出されない場合のデフォルトの挙動が不明確でした。

今回の変更では、このコメントに以下の重要な一文が追加されました。

If Seed is not called, the generator behaves as if seeded by Seed(1).

この一文により、開発者は Seed 関数を呼び出さない場合でも、グローバルな乱数生成器がシード 1 で初期化されるという、その決定論的な性質を明確に理解できるようになりました。これは、既存のコードの動作を変更するものではなく、その動作に関するドキュメントの正確性と完全性を向上させるものです。

このドキュメントの追加は、特に再現可能な乱数シーケンスが必要なテストやシミュレーションにおいて、開発者が意図的に Seed(1) を呼び出す必要がないことを示唆し、また、非決定論的な乱数が必要な場合には明示的に Seed(time.Now().UnixNano()) のような呼び出しを行うべきであることを強調します。

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

変更は src/pkg/math/rand/rand.go ファイルの Seed 関数のコメント部分にあります。

--- a/src/pkg/math/rand/rand.go
+++ b/src/pkg/math/rand/rand.go
@@ -107,7 +107,9 @@ func (r *Rand) Perm(n int) []int {
 
 var globalRand = New(&lockedSource{src: NewSource(1)})
 
-// Seed uses the provided seed value to initialize the generator to a deterministic state.
+// Seed uses the provided seed value to initialize the generator to a
+// deterministic state. If Seed is not called, the generator behaves as
+// if seeded by Seed(1).
 func Seed(seed int64) { globalRand.Seed(seed) }
 
 // Int63 returns a non-negative pseudo-random 63-bit integer as an int64.

コアとなるコードの解説

このコミットにおけるコアとなるコードの変更は、src/pkg/math/rand/rand.go ファイル内の Seed 関数のドキュメンテーションコメントの修正です。

元のコメント:

// Seed uses the provided seed value to initialize the generator to a deterministic state.

変更後のコメント:

// Seed uses the provided seed value to initialize the generator to a
// deterministic state. If Seed is not called, the generator behaves as
// if seeded by Seed(1).

この変更は、コードのロジックや動作自体には一切影響を与えません。Seed 関数の機能や、globalRand の初期化方法に変更はありません。しかし、このコメントの追加は、math/rand パッケージの利用方法に関する開発者の理解を大きく向上させます。

具体的には、以下の点が明確になります。

  1. デフォルトの決定論的挙動の明示: Seed 関数が一度も呼び出されない場合でも、グローバルな乱数生成器は常にシード 1 で初期化されるという、その決定論的な性質が明確に示されます。これにより、開発者はプログラムを複数回実行しても同じ乱数シーケンスが得られることを期待できます。
  2. 混乱の解消: 多くのプログラミング言語では、乱数生成器がデフォルトで非決定論的に(例えば、現在の時刻に基づいて)シードされるため、Goのこの挙動は予期せぬものとなる可能性がありました。このコメントは、その誤解を解消します。
  3. 適切な利用法の促進: 非決定論的な乱数が必要な場合(例: ゲーム、セキュリティ関連のアプリケーション)には、開発者が明示的に rand.Seed(time.Now().UnixNano()) のようなコードを記述する必要があることを暗に示唆しています。

この変更は、単なるドキュメントの更新でありながら、ライブラリの透明性と使いやすさを大幅に向上させる、非常に重要な改善と言えます。

関連リンク

参考にした情報源リンク