見出し画像

固定されたランダム 🎲: Goのランダム数

はじめに

ランダム数は、暗号化、シミュレーション、ゲームなど、コンピューティングのさまざまな分野で広く使用されています。ランダム数は、大きく分けて「真のランダム数」と「疑似ランダム数」の2種類に分類されます。

真のランダム数

真のランダム数は、コイントス、サイコロの振り、ルーレット、電子ノイズ、核分裂などの物理現象を使用して生成されます。このような方法に基づいてランダム数を生成する装置は、「物理的ランダム数生成器」と呼ばれます。

疑似ランダム数

疑似ランダムとは、見た目にはランダムであるが、実際にはそうではないプロセスを指します。例えば、疑似ランダム数は決定論的なアルゴリズムを使用して計算され、ランダムのように見える数列を生成します。

疑似ランダム数を計算するために使用される関数は「ランダム関数」と呼ばれ、ランダム関数を利用してランダム数を生成するアルゴリズムは「ランダム数生成器」と呼ばれます。一部のランダム関数は周期的であり、非周期的な関数の方が一般的に優れていますが、周期的なランダム関数は処理が高速であることが多いです。また、一部の周期的な関数は係数を調整可能で、非常に長い周期を実現できるため、非周期的な関数に近い効果を発揮します。

以下では、Golangにおける疑似ランダム数の実装について詳しく見ていきます。

初期のGoバージョンでのランダム数

// Go 1.18

import (
    "fmt"
    "math/rand"
)

func main() {
    fmt.Println(rand.Intn(100))
}

このコードを実行すると、何回実行しても結果が同じになります。なぜでしょうか?焦らずに実装を確認してみましょう。ソースコードを調べると、`math/rand`ライブラリはデフォルトのソースとしてシード値「1」を使用してランダム数を生成していることが分かります。前述のように、疑似ランダム数は決定論的なアルゴリズムによって生成されるため、真のランダムではありません。`NewSource`のシードが変更されない限り、プログラムを再起動するたびにランダム数列は同じになります。

ソースコード: rand.go (Go 1.18)

// ...

/*
 * Top-level convenience functions
 */

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

// ...

シードが毎回異なるようにするには、タイムスタンプをシードとして使用できます。これにより、プログラム実行ごとにランダム数列が変化します。

// Go 1.18

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    rand.Seed(time.Now().UnixNano())
    fmt.Println(rand.Intn(100)) // 実行するたびに結果が異なります
}

幸いなことに、Googleはこの問題をGo 1.20で修正しました。Go 1.20以降では、グローバルなランダム数生成器のシードは自動的に初期化され、デフォルトでは「1」に固定されません。環境変数`GODEBUG=randautoseed=0`が設定されていない限り、ランダム数列はプログラム実行ごとに変化します。

暗号的に安全なランダム数

`crypto/rand`ライブラリは、より安全なランダム数生成器を実装しています。コードコメントによると、Linux系プラットフォームでは、`getrandom(2)`システムコールを優先的に使用し、利用できない場合は`/dev/urandom`にフォールバックします。

`getrandom`は、`/dev/urandom`から高品質なランダム数を取得するためのシステムコールです。`/dev/urandom`は`/dev/random`をシードの参照元として使用し、`/dev/random`はハードウェア生成ノイズに基づいており、非常に高いランダム性を持ちます。

package rand

import "io"

// Readerは暗号的に安全なランダム数生成器の
// グローバルで共有されたインスタンスです。
//
// Linux、FreeBSD、Dragonfly、Solarisでは、Readerはgetrandom(2)を使用し、
// それが利用できない場合は/dev/urandomを使用します。
// OpenBSDおよびmacOSでは、Readerはgetentropy(2)を使用します。
// その他のUnix系システムでは、Readerは/dev/urandomから読み取ります。
// Windowsでは、ReaderはRtlGenRandom APIを使用します。
// Wasmでは、ReaderはWeb Crypto APIを使用します。
var Reader io.Reader

// Readは、Reader.Readを使用してio.ReadFullを呼び出す補助関数です。
// 戻り値では、n == len(b)となるのはerr == nilのときのみです。
func Read(b []byte) (n int, err error) {
    return io.ReadFull(Reader, b)
}

まとめ

  1. プログラムが生成するランダム数は疑似ランダム数です。

  2. Go 1.20以前の`math/rand`パッケージは、デフォルトの共有ソースとして固定シード(`seed=1`)を使用していたため、プログラム実行ごとにランダム数列が同じでした。Go 1.20以降では、シードが自動的に初期化され、シーケンスが毎回異なります(明示的に上書きされない限り)。

  3. `crypto/rand`パッケージは、暗号的に安全なランダム数生成器を提供します。

  4. `crypto/rand`は`math/rand`よりも安全ですが、パフォーマンスにコストがかかります。用途に応じて適切なライブラリを選択してください。暗号目的の場合は、常に`crypto/rand`を使用してください。

  5. JavaScriptでは、`crypto.getRandomValues`が暗号的に安全なランダム数生成器であり、`Math.random`よりも安全です。


私たちはLeapcell、Goプロジェクトのクラウドデプロイの最適解です。

Leapcellは、Webホスティング、非同期タスク、Redis向けの次世代サーバーレスプラットフォームです:

複数言語サポート

  • Node.js、Python、Go、Rustで開発できます。

無制限のプロジェクトデプロイ

  • 使用量に応じて料金を支払い、リクエストがなければ料金は発生しません。

比類のないコスト効率

  • 使用量に応じた支払い、アイドル時間は課金されません。

  • 例: $25で6.94Mリクエスト、平均応答時間60ms。

洗練された開発者体験

  • 直感的なUIで簡単に設定できます。

  • 完全自動化されたCI/CDパイプラインとGitOps統合。

  • 実行可能なインサイトのためのリアルタイムのメトリクスとログ。

簡単なスケーラビリティと高パフォーマンス

  • 高い同時実行性を容易に処理するためのオートスケーリング。

  • ゼロ運用オーバーヘッド — 構築に集中できます。

ドキュメントで詳細を確認!

Xでフォローする:@LeapcellHQ


ブログでこの記事を読む

いいなと思ったら応援しよう!