表題

乱数ビットのソース

著者

Sebastian Egner

状態

この SRFI は現在「確定」の状態である。 SRFI の各状態の説明については ここ を参照せよ。 この SRFI に関する議論については メーリングリストのアーカイブ を参照せよ。

概要

このドキュメントでは、乱数ビットの発生源に対するインターフェイスを定める。 乱数ビットの発生源のことを、以後簡単に「乱数ソース」(random source) と呼ぶことにする。 乱数ソースの品質や、乱数生成のプロセスの制御に対する 異なる要求レベルを満たすために、 このインターフェイスを利用するための3通りの方法を定める。

乱数ソースが乱数ビットを生成するための基盤があれば、 それを利用して別種の乱数ジェネレータを派生させることができる。 最も重要なのは、 様々な分布を持つ浮動小数点数や、ランダムな離散構造、たとえば、 順列 (premutation) やグラフを生成することである。 このような応用は無数にあるため、この SRFI では扱わない。 言い換えると、 この SRFI はランダムな あらゆる種類のランダムなオブジェクトを生成するための方法を定めるのではなく、 ランダムなビット列を移植性があり、柔軟性があり、信頼性があり、かつ、 効率性がある形で定めることである。

論拠

この SRFI では、疑似乱数ジェネレータにより計算される乱数ビットの発生源へのインターフェイスを定める。 このインターフェイスは、 範囲指定された整数や実数を生成することができ、 ジェネレータ内部の状態にアクセスすることができる。 さらに、大量の独立したジェネレータを取得して 真のランダム化に近い乱数を生成することができる。

この設計は、 離散構造、数値シミュレーション、暗号プロトコルなど、 数多くのアプリケーションにおける乱数の多様な使われ方をカバーできるように、 十分に柔軟であることを設計目標としている。 同時に、インターフェイスを単純にすることで、 予想できない使われ方にも対応できるようにしている。 しかし、すべての用途に万能な乱数ジェネレータを設計することは不可能なので、 様々なアプリケーションにおける様々な要求に対して、 いくらかの妥協を行っている。

厳密にいえばこの仕様の一部ではないが、 この設計では高品質高効率に重点をおいている。 疑似乱数ジェネレータはまだ大きく進歩している最中なので、 参照実装に使用したアルゴリズムは、仮のものであることをお断りしておく。

仕様

(random-integer n) -> x
xdefault-random-source が次に生成する {0, ..., n-1} の範囲の整数である。 この手続きを何度呼び出してもその結果は独立であり {0, ..., n-1} の範囲に一様に分布しているように見える。 引数 n は正整数でなければならない。 正整数でなければエラーが発生する。
(random-real) -> x
xdefault-random-source が次に生成する 0 < x < 1 の範囲の実数である。 この手続きを何度呼び出してもその結果は独立であり、 一様に分しているように見える。 戻り値の型および量子化は実装依存である。 詳細は random-source-make-reals を参照せよ。
default-random-source
random-source-make-integersrandom-source-make-reals を用いて random-integerrandom-real を派生させた乱数ソースである。 default-random-source 変数に代入を行っても、 randomrandom-real は変化しないことに注意せよ。 また、この変数に新しい値を代入しないことを強く推奨する。

(make-random-source) -> s
新しい乱数ソース s を作成する。 実装によっては、追加のオプション引数を指定することで、 異なるタイプのランダムソースを作成できるようにしてもよい。 make-random-source で作成された乱数ソースは、 疑似乱数ジェネレータにより生成される乱数ビットの決定論的ストリームである。 (make-random-source) により作成される乱数ソースは、 後述の手続きにより状態を変更されない限り、 常に同じ値のストリームを生成する。
(random-source? obj) -> bool
obj が乱数ソースであるか確認します。 乱数ソース タイプのオブジェクトは、 他のいかなるタイプのオブジェクトとも異なる。
(random-source-state-ref s) -> state
(random-source-state-set! s state)
乱数ソース s の現在の状態を取得・設定する。 state オブジェクトの構造は実装依存である。 このオブジェクトの唯一の移植性のある使用法は、 random-source-state-set! の引数に指定するだけである。 しかし、このオブジェクトは外部表現可能でなければならない。
(random-source-randomize! s)
乱数ソース s の状態を真にランダムな状態に設定するように試みます。 このランダム化の実際の品質は実装依存であるが、 少なくとも、乱数ソース s の状態は Scheme システムの起動ごとに異なる状態に変更される。
(random-source-pseudo-randomize! s i j)
乱数ソース s の状態を、 (i, j) 番目の独立な乱数ソースの初期状態に変更する。 ここで ij は 0 以上の整数である。 この手続きは、2 つの整数によりインデックス付けされた、 大量の独立した乱数ソースを作成するためにある (これら乱数ソースはしばしばバックボーンとなる同じジェネレータから作成される)。 random-source-randomize! とは異なり、 この手続きは完全に決定論的である。

(random-source-make-integers s) -> rand
乱数ソース s を用いて乱数整数を生成する手続き rand を取得する。 rand は 1 つの引数 n をとる。 この引数は正整数でなければならない。 {0, ..., n-1} の範囲に一様に分布した次の乱数整数を返す。 このとき、乱数ソース s の状態を更新する。

アプリケーションが同じ乱数ソース s の複数のジェネレータを取得する場合、 このいずれのジェネレータも s の状態を更新する。 したがって、これらのジェネレータは状態を共有しているので、 互いに同じ乱数整数を生成することはない。 このことは、1つの乱数ソースから派生された複数のジェネレータにも当てはまる。 並列処理をサポートする処理系では、 ジェネレータの状態が正しく更新されることを保証しなければならない。

(random-source-make-reals s) -> rand
(random-source-make-reals s unit) -> rand
乱数ソース s を用いて 0 < x < 1 の範囲の乱数実数を生成する手続き rand を取得する。 手続き rand は引数を取らない。

オプション引数の unit は、 手続き rand により生成される数値の型と量子化を指定する。 unit は 0 < unit < 1 の数値でなければならない。 rand により生成される数値は、 unit 引数と同じ数値型であり、 最大でも unit の間隔を持つ。 randx*unit という形式の数値を生成すると考えればよい。 ここで x は {1, ..., floor(1/unit)-1} の範囲の乱数整数である。 しかし、実際にこのように数値を生成する必要はないし、 rand の実際の分解能は unit よりもずっと高くてよい。 unit を指定しない場合は、 適度に小さい値がデフォルトとして使われる (このデフォルト値は効率的な数値フォーマットにおける仮数の幅に関係するだろう)。

設計の論拠

なぜ random-integerrandom-real を1つの関数にまとめないのですか?

この2つの手続きを1つの関数にまとめると、 可変個引数の手続きを定義することになるが、そうすると、 実行時の処理時間とメモリが少し余計に必要になる。 可変個引数を固定個引数と同じぐらい効率的に扱うことができる Scheme 処理系もあるが、 必ずしもそういう処理系ばかりではないし、 ここでは時間効率性を非常に重視するからである。

オブジェクト指向インターフェイスはないのですか?

この SRFI で定めたインターフェイスとは別の選択肢は多数ある。 特に、任意のオブジェクト指向フレームワークを用いて、 乱数ソースを表すクラスを定め、そののメソッドを定めることができる。 しかし、それぞれのオブジェクト指向フレームワークによって その構文と機能が大きく異なるので、 この SRFI では特定のフレームワークは使用しない。

範囲固定のジェネレータはないのですか?

範囲固定のジェネレータは、用途が非常に限られている。 ほとんど全てのアプリケーションでは、 乱数を利用するためのいくつかの機能が必要である。 その中でも最も基本的な機能は、 乱数の範囲と量子化を変更することである。 それがまさしく random-integerrandom-real により提供される機能である。 それに加えて、これらの機能があれば、ユーザーが単純な modulo 計算により乱数範囲を変更するという落とし穴に落ちなくて済む。 この方法により乱数範囲を変更すると、乱数の品質は大きく劣化するのである。 このインターフェイス設計は、次の3つのプロトタイプ アプリケーションに基づいている。
  1. 比較的小さな集合から繰り返し選択する: その集合のサイズは呼び出しごとに異なるので、 random-integer は呼び出しごとに範囲を定める引数 n を取る。 この手続きの実装では、範囲が即値整数であれば、値のボクシング/アンボクシングを回避すべきである。
  2. ビット数が固定されている大きな整数を少し生成する: 乱数を生成する処理自体が負荷のかかる処理なので、 呼び出しごとに乱数の範囲を引数に渡すことは、 全体の性能に影響しない。 したがって、前述のケースと同じインターフェイスを使えばよい。
  3. 実数を生成する: 集合から選択するケースとは異なり、 乱数の範囲と量子化は、呼び出しごとに一定であることが多い。 また、数値の型と量子化によって異なる少数の実装インスタンスが存在するのが普通である。 たとえば、内部的に使用している floatdouble の型によって異なるインスタンスが有り得るだろう。 したがって、random-real はパラメータを受け取らないが、 手続き random-source-make-reals によって適切に設定された random-real 手続きを作成できるように設計している。

なぜ浮動小数点数にこだわるのですか?

乱数ジェネレータの浮動小数点数に対する適切な実装は、 整数に対する実装よりもずっと効率的にできる可能性がある。 強力な算術ハードウェアを利用することができるからである。 また、アプリケーションが浮動小数点数の乱数を必要とするなら、 整数の乱数ジェネレータを用いて浮動小数点数を生成することは、 耐えられないほどの無駄だろう。 さらに、整数ジェネレータを実数ジェネレータに変換するという 見かけほど簡単ではない作業をユーザーに負担させたくないという理由もある。

random-real の戻り値から 0 と 1 が除外されているのはなぜですか?

手続き random-realx = 0 と x = 1 を返さないのは、 (log x)(log (- 1 x)) を計算するときに数値演算例外を起こさないようにするためである。

実装

ジェネレータの選択

実装における最も重要な決定は、乱数ジェネレータの選択である。 本提案における基本的な原則は、「品質を優先させる」である。 結局のところ、より良いジェネレータを用いることで生じる性能上のペナルティは、 大惨事を回避するという意味では、安い代償だろう。 思いも寄らないかもしれないが、 より良いジェネレータのほうが高速であったという経験が、私には数多くある。 単純な線形合同ジェネレータは、振る舞いがよくないことがあるので、推奨することはできない。

以上の理由から、私はまず George Marsaglia の COMBO ジェネレータを提案した。 このジェネレータは、32 ビットの乗法遅延 Fibonacci ジェネレータと、 16 ビットのキャリー乗算ジェネレータを組み合わせたものである。 COMBO ジェネレータは Marsaglia の DIEHARD 検査をすべてパスしており、2^60 のオーダーの周期を持つ。

改善案として、Brad Lucier は Pierre L'Ecuyer の MRG32k3a ジェネレータを提案している。 このジェネレータは、2つの3次再帰ジェネレータを組み合わせたものであり、 その2つともが 54 ビット算術内に収まるというものである。 MRG32k3a ジェネレータも DIEHARD 検査をパスしており、 それに加えて、好ましいスペクトル特性を持ち、2^191 というオーダーの周期を持つ。 実を言うと、多重再帰ジェネレータ (MRG: Multiple Recursive Generator) は、 COMBO ジェネレータのような特殊な構成のジェネレータよりも、 理論的により良く解析されている。 これらの理由から、ここでは MRG32k3a ジェネレータを実装することにした。 十分な仮数幅を持つ浮動小数点演算により実装された場合、 このジェネレータは非常に高速である。

算術の選択

実装において次に重要な決定事項は、算術の型である。 この選択は難しく、使用している Scheme システムや、ハードウェア プラットフォームや、 アーキテクチャに大きく依存する問題である。 MRG32k3a ジェネレータを実装する場合、可能であるなら、64 ビット算術を採用するのがよい。 それが無理であれば、54 ビット以上の仮数部をもつ浮動小数点 ALU を使うのがよい。 それも使えないなら、少なくとも (割り当てられたオブジェクトではなく) 即値整数で試してみるとよい。 残念なことに、Scheme ではネイティブ型とエミュレーション算術とを区別するための 可搬性のある方法は存在しない。

多くのアプリケーションにとっては性能が重要であるため、 実際のジェネレータをネイティブコードで実装したいと思うかもしれない。 そこで、参考までに、バックボーンとなるジェネレータを異なる3つの方法で実装した。 下記のコードを参照してほしい。

乱数ソースのデータ型

この SRFI 仕様の重要な側面は、 乱数ソースが既存の型とは異なるオブジェクトであるということである。 ほとんど全ての Scheme 処理系において、これは簡単に実現可能ではあるが、 現状では、移植性のある実装方法は存在しない。 1つの方法は、 SRFI-9 を使用してレコード型を定義することである。

後で解説する参照実装では、 エクスポートされた手続きを持つレコード型を定義する。 ジェネレータの実際の状態は、 make-random-source の束縛時の環境に格納される。 この実装方法には、レコード型の速度が遅い場合でも (遅い必要はないが)、 状態へのアクセスが高速であるという利点がある。

ランダム化のためのエントロピー源

本仕様において可搬性の点で難点があるもう1つの部分は、 random-source-randomize! である。 この手続きは現実のエントロピー源にアクセスする必要があるからである。

エントロピー源の手ごろな選択肢としては、 システム クロックを使う方法がある。 この方法は John David Stone (下記の参考文献を参照) が推奨している。 この方法はほとんどのアプリケーションでは十分良い選択肢ではあるが、 セキュリティ関連のプログラムでは使うことができない。 Scheme で時刻を取得するには、 SRFI-19 を使えばよい。

インターフェイスの実装

可搬性の問題が解決したなら、 この SRFI で定義された残り機能を提供することができる。

参照実装では、コードの比較的大部分が、 MRG32k3a ジェネレータのより高度な機能を扱っている。 特に random-source-pseudo-randomize! がそうである。 このコードは Pierre L'Ecuyer 自身による MRG32k3a ジェネレータの実装から影響を受けている。

Another part of this generic code deals with changing the range and quantization of the random numbers and with error checking to detect common mistakes and abuses.

実装の例

この SRFI の3つの異なる実装はここにある。 (ファイルはここにある。 .tar.gz ファイル, 13300 bytes である。) SRFI とは「実装の要望」(request for implementation) であることに注意してください。 つまり、これらは仕様を説明するための単なる実装例に過ぎず、 より良い、高性能な実装を促す目的でのみ提供している。 以下に示す性能値は、Pentium3, 800 MHz の Linux 上で計測された概略値である。 x int/s, y real/s という数値は、 1 秒間に (random-integer 2)x 回計算可能であること、 1 秒間に (random-real)y 回計算可能であることを意味している。 実装は:
  1. Scheme 48 0.57 で 54 ビット integer のみを用いた実装。 この実装は性能よりも移植性を重視している。 (30000 ints/s, 3000/s reals/s)
  2. Scheme 48 0.57 でコアとなるジェネレータを C 言語で実装し (double) 算術を用いた実装。 このジェネレータは、 C/Scheme インターフェイス を介して Scheme 48 で利用可能である。 このジェネレータの性能は良好である。 (160000 ints/s, 180000 reals/s)
  3. Gambit 3.0 で flonum と 54 ビット integer を用いた実装。 このコードは Brad Lucier が SRFI 議論アーカイブに 投稿した プログラムを参考にしている。 このジェネレータは、コンパイル時の性能は良好である。 (インタープリタ実行時の性能 5000 ints/s, 25000/s reals/s , コンパイル後の性能は 200000 ints/s, 400000/s reals/s である。 後述の謝辞を参照せよ。)
これらの実装に加えて、指定されたインターフェイスに対する 信頼テスト をいくつか用意した。 これらのテストは、この仕様で定義されているいくつかのアサーションをチェックするだけである。 インターフェイスの完全なテストを提供するものではない。 ましてや、ジェネレータの統計的な検査を行うものではない。 しかし、ジェネレータが生成した乱数ビットを、 DIEHARD 検査が読み込める形式でファイルに書き込むための関数を用意している。 実装者が好みのジェネレータを見つけてきて、 その実装を検査するのに役立つだろう。

推奨される使用パターン

この SRFI で定義される機能が十分でない限り、 アプリケーションは乱数を特定の目的のために利用する手続きを実装しなくてはならない。 この節では、このような手続きを実装する場合の推奨事項を示す。<-- (★) --> 以下のコードは仕様の一部ではなく、あくまで説明のためのものであることをお断りしておく。

ランダム順列の生成

以下のコードは、集合 {0, ..., n-1} のランダム順列を生成する手続きを定義している。 順列は長さ nvector で表現している。

この実装では、まず、乱数ソース sn 次の順列を生成する手続きに変換する手続き random-source-make-permutations を定義している。 次に、デフォルトの乱数ソースに対してこの手続きを適用し、 すぐに順列を生成できる手続きを定義している。 手続き (random-permutation n)n 次のランダム順列を生成する。

(define (random-source-make-permutations s)
  (let ((rand (random-source-make-integers s)))
    (lambda (n)
      (let ((x (make-vector n 0)))
	(do ((i 0 (+ i 1)))
	    ((= i n))
	  (vector-set! x i i))
	(do ((k n (- k 1)))
	    ((= k 1) x)
	  (let* ((i (- k 1))
		 (j (rand k))
		 (xi (vector-ref x i))
		 (xj (vector-ref x j)))
	    (vector-set! x i xj)
	    (vector-set! x j xi)))))))

(define random-permutation
  (random-source-make-permutations default-random-source))
このアルゴリズムについては Knuth の "The Art of Computer Programming", Vol. II, 2nd ed., Algorithm P of Section 3.4.2. を参照せよ。

指数分布を持つ乱数の生成

以下のコードは、指数分布 Exp(mu) を持つ乱数を生成する手続きを定義している。 ここで定義しているインターフェイスの技術的な難しさは、 いかにして random-source-make-reals にオプション引数を渡すかということにある。
(define (random-source-make-exponentials s . unit)
  (let ((rand (apply random-source-make-reals s unit)))
    (lambda (mu)
      (- (* mu (log (rand)))))))

(define random-exponential
  (random-source-make-exponentials default-random-source))
このアルゴリズムについては Knuthの "The Art of Computer Programming", Vol. II, 2nd ed., Section 3.4.1.D. を参照せよ。

正規分布を持つ乱数の生成

以下のコードは、正規分布 N(mu, sigma) を持つ実数を polar method を用いて生成しています。

ここで定義しているインターフェイスの技術的な難しさは、 polar method が計算のたびに2つの結果を生成することに関係している。 ここでは1つめ結果のみを返し、2つめの結果は記録しておいて、 次回の手続き呼び出し時に返すようにしている。 したがって、 random-source-state-set! (あるいは状態を変更する他の手続き) は必ずしもすぐには random-normal の出力に影響しない!

(define (random-source-make-normals s . unit)
  (let ((rand (apply random-source-make-reals s unit))
	(next #f))
    (lambda (mu sigma)
      (if next
	  (let ((result next))
	    (set! next #f)
	    (+ mu (* sigma result)))
	  (let loop ()
	    (let* ((v1 (- (* 2 (rand)) 1))
		   (v2 (- (* 2 (rand)) 1))
		   (s (+ (* v1 v1) (* v2 v2))))
	      (if (>= s 1)
		  (loop)
		  (let ((scale (sqrt (/ (* -2 (log s)) s))))
		    (set! next (* scale v2))
		    (+ mu (* sigma scale v1))))))))))

(define random-normal
  (random-source-make-normals default-random-source))
このアルゴリズムについては Knuth の "The Art of Computer Programming", Vol. II, 2nd ed., Algorithm P of Section 3.4.1.C. を参照せよ。

謝辞

議論に参加してくれた全ての人々に感謝する。 特に Brad Lucier と Pierre l'Ecuyer に感謝する。 彼らの貢献により、この SRFI の設計を大きく改善することができた。 また、Brad は Gambit による実装を大きく最適化してくれた。

参考文献

  1. G. Marsaglia: Diehard -- 乱数ジェネレータの検査スイート。 stat.fsu.edu/~geo/diehard.html (Diehard をパスしたジェネレータがいくつか含まれている)
  2. D. E. Knuth: The Art of Computer Programming; Volume II Seminumerical Algorithms. 2nd ed. Addison-Wesley, 1981. (乱数ジェネレータに関する有名な章である)
  3. P. L'Ecuyer: "Software for Uniform Random Number Generation: Distinguishing the Good and the Bad", Proceedings of the 2001 Winter Simulation Conference, IEEE Press, Dec. 2001, 95--105. www.iro.umontreal.ca/~lecuyer/myftp/papers/wsc01rng.pdf (乱数ジェネレータに関する深い議論がなされている)
  4. P. L'Ecuyer: "Good Parameter Sets for Combined Multiple Recursive Random Number Generators", Shorter version in Operations Research, 47, 1 (1999), 159--164. www.iro.umontreal.ca/~lecuyer/myftp/papers/combmrg2.ps (Actual numbers for good generators.)
  5. P. L'Ecuyer: "Software for Uniform Random Number Generation: Distinguishing the Good and the Bad", Proceedings of the 2001 Winter Simulation Conference, IEEE Press, Dec. 2001, 95--105.
  6. MIT Scheme v7.6: random flo:random-unit *random-state* make-random-state random-state? http://www.swiss.ai.mit.edu/projects/scheme/documentation/scheme_5.html#SEC53 (A mechanism to run a fixed unspecified generator.)
  7. A. Jaffer: SLIB 2d2 with (require 'random): random *random-state* copy-random-state seed->random-state make-random-state random:uniform random:exp random:normal-vector! random-hollow-sphere! random:solid-sphere! http://swiss.csail.mit.edu/~jaffer/slib_5.html#SEC108 (RC-4 を使用している)
  8. R. Kelsey, J. Rees: Scheme 48 v0.57 'random.scm': make-random make-random-vector (Scheme48 の内部手続き、固定長 28 ビット ジェネレータ)
  9. M. Flatt: PLT MzScheme Version 200alpha1: random random-seed current-pseudo-random-generator make-pseudo-random-generator pseudo-random-generator? http://download.plt-scheme.org/doc/200alpha1/html/mzscheme/mzscheme-Z-H-3.html#%_idx_144 (ジェネレータを実行する仕組みとジェネレータを交換する仕組みについて)
    ([訳注]上記のリンクは切れている。おそらく次のリンク先と同じだろう。
    http://download.plt-scheme.org/doc/200/html/mzscheme/mzscheme-Z-H-3.html#%_idx_184)
  10. H. Abelson, G. J. Sussmann, J. Sussman: Structure and Interpretation of Computer Programs. http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-20.html#%_idx_2934 (rand の例は乱数ジェネレータを定義するための教科書的な方法を示している)
  11. John David Stone: A portable random-number generator. http://www.math.grin.edu/~stone/events/scheme-workshop/random.html (Scheme による線形合同ジェネレータの実装)
  12. Network Working Group: RFC1750: Randomness Recommendations for Security. http://www.cis.ohio-state.edu/htbin/rfc/rfc1750.html (serious なセキュリティのための、serious なランダム性に関する、serious な議論)
  13. http://www.random.org/essay.html
    http://www.taygeta.com/random/randrefs.html (乱数ジェネレータとランダム性に関するリソース)

著作権

Copyright (C) Sebastian Egner (2002). All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


編集者: Mike Sperber
著作者: Sebastian Egner
最終更新日: Sun Jan 28 13:40:30 MET 2007