Permuted congruential generator
Permuted congruential generator (PCG) は2014年に発表された擬似乱数生成器。線形合同法の出力を加工することで統計的性質を改善したものである。高速、省メモリかつ小さいコードサイズで非常に良い統計的性質を示す[1][2][3][4][5]。
PCGは古典的な線形合同法と3つの点で異なる。
- 線形合同法で使われる状態が大きい(通常、出力の2倍)。
- 法として2の冪を使う(これにより偏りがなく最長の周期を効果的に実装できる)。
- 状態は直接出力されるのではなく、最も周期の長いビットによってビット回転やビットシフトの量が決まり、出力に影響する。
下位ビットの短い周期という線形合同法の弱点は状態に依存するビット回転量により解決する[5]。
PCGの種類
[編集]PCGにはいくつかの種類がある。内部で使用する線形合同法は8ビットから128ビットのものが使用される。実用的な用途では64ビットと128ビットのものだけが推奨される。それより小さいものは統計的性質のテストに使用される。
線形合同法で加算される定数は異なる乱数列を生成するために異なる値を使用できる[6]。定数は任意の奇数で、明示的に指定する必要はない。最下位のビットを1にすれば状態を保持する変数のアドレスを使ってもよい。
状態を出力に変換する方法がいくつかある。どの方法もうまく働くが、それぞれ余裕の幅が異なる[5]。変換方法は以下の要素によって構築される。
- RR: ランダムな(入力に依存する)ビット回転で、入力の半分の長さの出力になる。ビットの入力の最上位のビットが回転量を決めるのに使われ、そのすぐ下のビットが右に回転され出力され、残りの下位ビットは捨てられる。
- RS: ランダムな(入力に依存する)ビットシフト。ビット回転の計算時間が長いときに使用する。これも入力の半分の長さの出力になる。ビットの入力の最上位のビットによりシフト量が決定され、すぐ下のビットに適用され、その中の下位ビットが出力される。残りの下位ビットは捨てられる。
- XSH: Xorshiftの操作 (
x ^= x >> (定数)
)。定数は次の操作で捨てられないビットの半分(端数切り捨て)となるようにする。 - XSL: XSHの (定数) の部分を全体の長さの半分にしたもの。
- RXS: XSHの (定数) の部分をランダムな(入力に依存する)量にしたもの。
- M: 定数の乗算。
以下に挙げるものは推奨される組み合わせである。擬似コードも示す。
- XSH-RR: xorshiftで上位のビットを下位のビットに混ぜ、ビット 63-59 で回転量を決定しビット 27-58 に適用する。
- (64→32ビット)
count = (int)(x >> 59); x ^= x >> 18; return rotr32((uint32_t)(x >> 27), count);
- (64→32ビット)
- XSH-RS: 上の方法よりシフト量を決めるのに使われるビットが少ない。
- (64→32ビット)
count = (int)(x >> 61); x ^= x >> 22; return (uint32_t)(x >> (22 + count));
- (64→32ビット)
- XSL-RR: XSH-RRを単純化したもので、これは64ビットマシンで128ビットの状態を持つ実装に最適化されている。
- (128→64ビット)
count = (int)(x >> 122); x64 = (uint64_t)(x ^ (x >> 64)); return rotr64(x64, count);
- (128→64ビット)
- RXS-M-XS: 状態の半分を出力する場合、この方法は最も遅いが最も強い方法である。以下に示すように状態の全体を出力する場合は最も弱い方法になる。これを使う場合は状態のサイズが32ビットまたは64ビットに制限される。
- (32→32ビット)
count = (int)(x >> 28); x ^= x >> (4 + count); x *= 277803737u; return x ^ (x >> 22);
- (64→64ビット)
count = (int)(x >> 59); x ^= x >> (5 + count); x *= 12605985483714917081u; return x ^ (x >> 43);
- (32→32ビット)
- XSL-RR-RR: RXS-M-XSと同様に128ビットの状態から128ビットの状態に変換する。
- (128→128ビット)
count = (int)(x >> 122); low64 = rotr64((uint64_t)(x ^ (x >> 64)), count); high64 = rotr((uint64_t)(x >> 64), low64 & 63); return (uint128_t)high64 << 64 | low64;
- (128→128ビット)
それぞれの変換は元に戻すことができる(つまり全単射)か切り捨てである。したがってそれらを合成したものはある出力が得られる入力の個数は常に同じである。
もしより長い周期が必要になった場合は複数の生成器を使って拡張することができる。例えば生成器を2つ使う場合、生成器1の出力が0になるごとに生成器2の出力を1つ進めて、最終的な出力は生成器1の出力と生成器2の出力の和とすることでそれぞれの周期の積の周期になる。
コード例
[編集]ほとんどの場合に推奨される生成器は64ビットの状態を持ち32ビットを出力するPCG-XSH-RRである[5]。具体的な実装の一例を示す。
#include <stdint.h>
static uint64_t state = 0x4d595df4d0f33173; // 何らかの初期状態
static uint64_t const multiplier = 6364136223846793005u;
static uint64_t const increment = 1442695040888963407u; // 任意の奇数
static uint32_t rotr32(uint32_t x, unsigned r)
{
return x >> r | x << (-r & 31);
}
uint32_t pcg32(void)
{
uint64_t x = state;
unsigned count = (unsigned)(x >> 59); // 59 = 64 - 5
state = x * multiplier + increment;
x ^= x >> 18; // 18 = (64 - 27)/2
return rotr32((uint32_t)(x >> 27), count); // 27 = 32 - 5
}
void pcg32_init(uint64_t seed)
{
state = seed + increment;
(void)pcg32();
}
この実装ではスーパースカラーのプロセッサーで命令レベルの並列性を最大化するために変換を線形合同法の計算後に行っている[5]。
加算を使用せず、XSH-RSを使うことで若干高速化した実装を示す。この実装では周期はになり、XSH-RRより弱くなる。
static uint64_t mcg_state = 0xcafef00dd15ea5e5u; // 奇数でなければならない
uint32_t pcg32_fast(void)
{
uint64_t x = mcg_state;
unsigned count = (unsigned)(x >> 61); // 61 = 64 - 3
mcg_state = x * multiplier;
x ^= x >> 22;
return (uint32_t)(x >> (22 + count)); // 22 = 32 - 3 - 7
}
void pcg32_fast_init(uint64_t seed)
{
mcg_state = 2*seed + 1;
(void)pcg32_fast();
}
時間のかかる64ビットの乗算が残っているため高速化の効果は小さく、極端な場合を除き最初に挙げた実装のほうが良い。ただし、この高速化した実装も統計的検定を満足する[4]。
32ビットプロセッサーで実行する場合、64ビット同士の乗算は32ビット同士の乗算3回が必要になる。これを2回に減らすには、32ビットの範囲に収まる乗数(0xf13283adなど[6])を使用する。
他の生成器との比較
[編集]PCGはTestU01のBigCrushに合格するのに必要な最小限の状態のビット数を決定する過程で開発された[7]。BigCrushは周期2^35の列を検出するのに十分なデータをテストするため、理想的な乱数生成器でも36ビットが必要である。十分に大きい状態の場合、平方採中法のような非常に悪い生成器でもBigCrushに合格することがある[8]。小さい状態で合格することはそのアルゴリズムの質を示し、余裕の幅がどれだけあるかも示す。
PCG-RXS-M-XSはBigCrushに36ビット(理論上最小)で合格し、PCG-XSH-RRは39ビット、PCG-XSH-RSは49ビット必要である。ちなみに、xorshift*は40ビット[5]、そしてメルセンヌ・ツイスタは19937ビットもの状態を持つのにもかかわらずBigCrushに合格できない[9]。
関連項目
[編集]参考文献
[編集]- ^ Lemire, Daniel (22 August 2017). “Testing non-cryptographic random number generators: my results”. 2017年10月3日閲覧。
- ^ Cook, John D. (7 July 2017). “Testing the PCG random number generator”. 2017年10月3日閲覧。
- ^ Cook, John D. (14 August 2017). “Testing RNGs with PractRand”. 2017年10月3日閲覧。
- ^ a b O'Neill, M.E. (29 July 2017). “PCG Passes PractRand”. 2017年11月3日閲覧。
- ^ a b c d e f O'Neill, Melissa E. (5 September 2014). PCG: A Family of Simple Fast Space-Efficient Statistically Good Algorithms for Random Number Generation (PDF) (Technical report). Harvey Mudd College. HMC-CS-2014-0905。
- ^ a b O'Neill, M.E. (10 August 2017). “Critiquing PCG's streams (and SplitMix's too)”. 2017年11月3日閲覧。
- ^ O'Neill, M.E. (20 August 2017). “Visualizing the Heart of Some PRNGs”. 2017年11月3日閲覧。
- ^ O'Neill, M.E. (20 August 2017). “Too Big to Fail”. 2017年11月3日閲覧。
- ^ L'Ecuyer, Pierre; Simard, Richard (August 2007). “TestU01: A C library for empirical testing of random number generators”. ACM Transactions on Mathematical Software 33 (4): 22-1–22-40. doi:10.1145/1268776.1268777 .