PCG32徹底解説:64ビットLCGとXSH-RR出力で実現する高速・軽量な乱数生成の実務ガイド

はじめに — PCG32とは何か

PCG32(Permuted Congruential Generator, 32ビット出力)は、Melissa O'Neill によって提案された PCG ファミリのうち最も広く使われているバリアントの一つです。内部状態に 64ビットの線形合同法(LCG)を用い、32ビットの出力を「XSH-RR」と呼ばれる出力関数で変換します。狙いは「単純で高速、メモリ効率が良く、統計的性質も良好」というバランスの良い疑似乱数生成器を提供することです。

基本的な設計方針と特徴

  • 内部状態(state)は 64ビット。遷移は線形合同法(LCG)で行う。
  • 出力は 32ビットで、内部状態からの単純な切り出しではなく「XOR シフト(XSH)→回転(RR)」の変換を行うことでビット混合を高める。
  • ストリーム識別用の増分(increment、inc)を持ち、異なるストリーム(ストリームごとに異なる inc)で衝突しない独立系列を得やすくしている。
  • 設計上、暗号学的安全性は想定されていないが、統計的試験では良好な結果を示すものが多い。

アルゴリズムの詳細(PCG32 の具体例)

PCG32 の典型的な実装は次のようなロジックです(C 言語風の擬似コードで説明します)。

uint32_t pcg32_random_r(pcg32_random_t *rng) {
    uint64_t oldstate = rng->state;
    rng->state = oldstate * 6364136223846793005ULL + (rng->inc | 1ULL);
    uint32_t xorshifted = (uint32_t)(((oldstate >> 18u) ^ oldstate) >> 27u);
    uint32_t rot = (uint32_t)(oldstate >> 59u);
    return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
}

主なポイント:

  • 乗数(multiplier)は 6364136223846793005(0x5851f42d4c957f2d)とされることが多い。
  • 増分(inc)は奇数に設定する((initseq << 1) | 1 のように)。モジュロ 2^64 の LCG では、増分が奇数なら周期が 2^64 になる。
  • 出力関数は「xorshift high(XSH)」で高位ビットを取り出してから「回転(RR)」でビットを位置的に混ぜる。回転量も状態の高位ビットから取るため、単純な切り出しよりも高いビット混合効果が得られる。

状態遷移と周期

状態遷移は 64ビット LCG(state = state * multiplier + inc mod 2^64)で行われます。増分を奇数にすると、この遷移は全域(full period)で 2^64 の周期を持ちます。つまり、各ストリーム(inc の値で定まる)について状態は 2^64 サイクルで周回します。

出力は 32ビットなので、出力系列が示す周期は理論上 2^64 まであり得ますが、出力ビット幅や変換により見かけ上の特性は異なり得ます。

シード(初期化)の扱い方

PCG の公式実装ではシードのために二段階の初期化が推奨されています。これは LCG の初期状態の特性を考慮して、初期化直後の出力が偏らないようにするためです。典型的な初期化は次の順序です:

  • rng->state を 0 に設定する。
  • rng->inc に (initseq << 1) | 1 を設定する(ストリーム識別子)。
  • 一度 pcg32_random_r を呼んで状態を進める。
  • rng->state に initstate を加える。
  • 再度 pcg32_random_r を呼んで最初の乱数を得る。

この手順により、同じ initstate でも initseq(ストリーム)が異なれば独立した系列が得られるよう設計されています。

統計的性質とテスト結果

PCG ファミリは設計目的として統計的性質の良さを重視しており、PCG32(XSH-RR 64/32)も多くの標準的な乱数性テストで良好な結果を示します。オリジナル論文および公式サイトでは TestU01(SmallCrush/Crush 等)や PractRand などのテスト結果が公開されています。

ただし注意点:

  • PCG は暗号用 PRNG ではないため、暗号用途には使えない(推奨されない)。
  • LCG を基礎にしているため、設計や実装を誤ると周期や相関に問題が出る可能性がある(例えば増分を偶数にするなど)。
  • 統計試験は有限のデータ量で行われるため、実際の利用用途(特に数値シミュレーションやモンテカルロなど)では目的に応じて十分な検証を行うべきである。

PCG と他のジェネレータ(例:MT19937)との比較

代表的な比較点を挙げます:

  • 状態サイズ:MT19937 は 19937 ビットの内部状態を持ち、非常に長い周期(2^19937−1)を有する。一方で PCG32 は 64ビットの状態(内部的には state と inc を持つため 128ビットの情報)で小さい。
  • 速度とメモリ:PCG32 は小さく高速で、組込環境やキャッシュ効率が重要な場面で有利。MT19937 は大量の状態を扱うため初期化やキャッシュ面で不利になり得る。
  • 統計特性:どちらも良好だが、MT は非常に長い周期を確保する設計であり、大量生成や長期間の独立系列が必要な場合に強みを持つ。PCG は小さい状態で「充分に良い」性質を目指している。
  • 暗号用途:どちらも暗号学的に安全ではない。

長所・短所(実務観点)

  • 長所: 実装が簡単で高速、メモリ小さい、並列ストリーム(increment)で系列分割が容易、公式実装/ドキュメントが充実している。
  • 短所: 暗号学的用途には不向き。極端に長期・非常に大規模な乱数生成を行う用途では状態サイズの小ささが制約になることがある。

実装上の注意点とベストプラクティス

  • 必ず増分(inc)を奇数にする。公式シードルーチンに従うこと。
  • 複数スレッドで独立系列を使う場合、各スレッドで異なるストリーム(initseq)を割り当てる。単純に同じシードを並行して使うと相関が出る。
  • 暗号用途ではないことを明示する(API やドキュメントで)。もし暗号的安全性が必要ならば、CSPRNG(例:/dev/urandom、libsodium、ChaCha20-PRNG など)を使う。
  • シード管理:再現性が重要ならば seed(initstate と initseq)を確実に保存・管理する。デフォルトシードや固定シードを使う場合は被ることに注意。

実用例と用途

PCG32 は次のような用途に向いています:

  • ゲーム開発(高速かつ軽量な乱数が欲しい場合)
  • 科学計算・モンテカルロ法(状態サイズと速度のバランスが良い)
  • 組み込みシステムやモバイルアプリ(メモリ・性能制約が厳しい環境)

反対に、暗号鍵生成やセキュアトークン生成などセキュリティ上の要件がある用途では避けるべきです。

拡張と他のPCGバリアント

PCG ファミリには PCG32 の他にも 64ビット出力や 128ビット状態を持つバリアントなどが存在します(例:pcg64、pcg64_xsl_rr_128_64 など)。用途に応じて出力幅や内部状態のサイズを選ぶことが可能です。

まとめ

PCG32 は「シンプルで実用的」な疑似乱数生成器として非常に有用です。小さいメモリフットプリント、高速な生成、良好な統計的性質が特徴で、ゲームやシミュレーション、組み込み用途などで広く使えます。ただし暗号学的なセキュリティは提供しないため、用途を明確に分類して選ぶことが重要です。公式実装や論文、FAQ に従って初期化・シード管理を適切に行えば、非常に安定して使える PRNG です。

参考文献