RSS

 

RSS


プ:擬似乱数って

  • いわいまさか
  • at 2008/7/18 18:50:33

 麻雀の山配りのときに出てくる問題点を提示しようしている。その前に今回は、擬似乱数の説明。

1.擬似乱数
  Cのrand()やC#(.NET)のRandomは「擬似乱数」。定義はwikipediaなどで。

2.毎回同じ
  このプログラムを動かしてみる

 for(;;){
  Random r = new Random(0);  //ここでは明確にシードを0と指定
  for(Int32 i=0;i<30;i++){
   Console.Write("{0:d}",r.Next(100));
  }
 Console.Write("\n");
  }     

結果はこれ。
72 81 76 55 20 55 90 44 97 27 29 46 63 46 98 03 86 99 67 31
72 81 76 55 20 55 90 44 97 27 29 46 63 46 98 03 86 99 67 31
72 81 76 55 20 55 90 44 97 27 29 46 63 46 98 03 86 99 67 31 
・・・

このように、シードが同じならば返り値の振る舞いがいつもいっしょ。(実は、この再現性があるという性質はテストやデバッグには重宝する。)

3.内部状態とループ
 擬似乱数器の内部状態のとりうる状態が有限ならば、いつかはループする。

 以下はそれを自前のTinyRandを作って確認してみた。「簡単のため10回続けて同じならば、もう同じと判定している」

// 自前の擬似乱数発生 wikipediaの混合合同法をほぼマルパクリ。
class TinyRand
{
    Int64 m_a;
    Int64 m_p;
    Int64 m_q;

    //          14992×673+944 = 10090560 → 09056
    public TinyRand()
    {
        m_a=14992;
        m_p=673;
        m_q=944;
    }

    public Int32 Next()
    {
        m_a = ((m_a * m_p + m_q) /10)%1000000000;
        return ((Int32)m_a);
    }
}

// 比較のため10回分は記憶しておく
class TameRand
{
    const Int32 ten = 10;
    TinyRand m_random;
    Int32   m_cursor;
    Int32[] m_tame;

public TameRand()
{
    m_random = new TinyRand();
    m_tame = new Int32[ten];

    for (Int32 i = 0; i < ten; i++)
    {
        m_tame[i] = m_random.Next();
    }
    m_cursor = 0;
}

public Int32 Next()
{
    Int32 ret=m_tame[m_cursor];
    m_tame[m_cursor]=m_random.Next();
    m_cursor=(m_cursor+1)%ten;
    return (ret);
}

public bool Equals(TameRand tr)
{
    for (Int32 i = 0; i < ten; i++)
    {
        if (tr.m_tame[(tr.m_cursor + i) % ten] != m_tame[(m_cursor + i) % ten])
        {
            return (false);
        }
    }
    return (true);
}


static void Main(string[] args)
{
// 「ウサギとカメ方式」でループチェック
{
    TameRand ur = new TameRand();
    TameRand kr = new TameRand();

    for (Int32 i=1; ;i++)
    {
        ur.Next(); // うさぎ君は2倍すすませて
        if (ur.Next() == kr.Next())
        {
            Console.Write("ループしたか? i={0:d}\n",i);
            if (ur.Equals(kr))
            {
                Console.Write("ループしたようだ! i={0:d}\n",i);
                for (; ; ) { }
            }
        }
    }
}
}

結果は
ループしたかなぁ? i=38904
ループしたようだ。 i=38904

 38904番目からの10個とその倍の77808番目からの10個の数字がまるっきり同じ。実際には10個だけでなく、そこからずっといっしょ。

 実は、同じ方法でC#のRandomのループ状態を確認しようとしたが、数分かけても捕まえられず。Cのrand()ならもっと短い周期でループしていたように記憶しているが。


  • コメント (0)
  • トラックバック (0)
トラックバックURL :
http://www.iwai-masaka.jp/tb.cgi/52302