悪い乱数のリスクを視覚的に明らかにする

ここでは良い乱数・悪い乱数で説明した 「上位ビットの問題」が持つリスクを明らかにする。
例としてC#と Random クラスを用いた、台風進路予想の真似事をしたプログラムを使っている。
進路予想は、座標(x,y)を次のように求めるような
    x += rng.NextDouble() + 1.5;
    y += rng.NextDouble() + 1.5;
ごく単純なものである。
これを10回繰返したものを一つの進路予想としている。
この進路予想を100回繰返して画面に進路を表示する。
このテストを Typhoon Test と呼ぶことにした。
プログラムは以下の2つのモードを持つ 結果はそれぞれのモードで以下のようになった。




シングルスレッドのシミュレーションでは 通常「1回だけ時刻を種にして初期化」が使われるので、問題はない。
しかし、マルチスレッドで高速化する場合、各々のスレッドで乱数の生成器を 用意して、違う種で初期化を行うことはよくあることである。
このような時「毎回種を1ずつ増やして初期化」のように初期化すると、 明らかに規則的な結果をもたらし、信頼性が悪くなる。
以下に完全なソースコードを示す。
※Seed の宣言と button1_Click メソッド以外は C# が生成したコードである。
/* RandTest.cs */
using System;
using System.Drawing;
using System.Windows.Forms;

class Form1:Form {
    static int Seed=0;
    private void button1_Click(object sender,EventArgs e)
    {
        if (listBox1.SelectedIndex < 0) listBox1.SelectedIndex = 0;
        Random rng = new Random();
        pictureBox1.Image = new Bitmap(pictureBox1.Width, pictureBox1.Height);
        Graphics g = Graphics.FromImage(pictureBox1.Image);
        SolidBrush pen0 = new SolidBrush(Color.Green);
        SolidBrush pen1 = new SolidBrush(Color.Red);
        for (int i = 0; i < 100; i++)
        {
            double x = 0, y = 0;
            if (listBox1.SelectedIndex == 1) rng = new Random(Seed++);
            for (int j=0;j<10;j++)
            {
                x += rng.NextDouble() + 1.5;
                y += rng.NextDouble() + 1.5;
                g.FillRectangle(j % 2 == 0 ? pen0 : pen1, (float)x * 20,
                    pictureBox1.Height - (float)y * 20, 3, 3);
            }
        }
        g.Dispose(); pen0.Dispose(); pen1.Dispose();
    }

    public Form1()
    {
        InitializeComponent();
    }

    /// <summary>
    /// 必要なデザイナー変数です。
    /// </summary>
    private System.ComponentModel.IContainer components = null;

    /// <summary>
    /// 使用中のリソースをすべてクリーンアップします。
    /// </summary>
    /// <param name="disposing">マネージ リソースが破棄される場合 true、破棄されない場合は false です。</param>
    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }

    #region Windows フォーム デザイナーで生成されたコード

    /// <summary>
    /// デザイナー サポートに必要なメソッドです。このメソッドの内容を
    /// コード エディターで変更しないでください。
    /// </summary>
    private void InitializeComponent()
    {
        this.button1 = new System.Windows.Forms.Button();
        this.listBox1 = new System.Windows.Forms.ListBox();
        this.pictureBox1 = new System.Windows.Forms.PictureBox();
        ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit();
        this.SuspendLayout();
        // 
        // button1
        // 
        this.button1.Location = new System.Drawing.Point(6, 6);
        this.button1.Name = "button1";
        this.button1.Size = new System.Drawing.Size(55, 22);
        this.button1.TabIndex = 0;
        this.button1.Text = "実行";
        this.button1.UseVisualStyleBackColor = true;
        this.button1.Click += new System.EventHandler(this.button1_Click);
        // 
        // listBox1
        // 
        this.listBox1.FormattingEnabled = true;
        this.listBox1.ItemHeight = 12;
        this.listBox1.Items.AddRange(new object[] {
            "1回だけ時刻を種にして初期化",
            "毎回種を1ずつ増やして初期化"});
        this.listBox1.Location = new System.Drawing.Point(6, 34);
        this.listBox1.Name = "listBox1";
        this.listBox1.Size = new System.Drawing.Size(160, 28);
        this.listBox1.TabIndex = 1;
        // 
        // pictureBox1
        // 
        this.pictureBox1.BackColor = System.Drawing.SystemColors.Window;
        this.pictureBox1.Dock = System.Windows.Forms.DockStyle.Fill;
        this.pictureBox1.Location = new System.Drawing.Point(0, 0);
        this.pictureBox1.Name = "pictureBox1";
        this.pictureBox1.Size = new System.Drawing.Size(500, 500);
        this.pictureBox1.TabIndex = 2;
        this.pictureBox1.TabStop = false;
        // 
        // Form1
        // 
        this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
        this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
        this.BackColor = System.Drawing.SystemColors.Control;
        this.ClientSize = new System.Drawing.Size(500, 500);
        this.Controls.Add(this.listBox1);
        this.Controls.Add(this.button1);
        this.Controls.Add(this.pictureBox1);
        this.Name = "Form1";
        this.Text = "Form1";
        ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit();
        this.ResumeLayout(false);

    }

    #endregion

    private System.Windows.Forms.Button button1;
    private System.Windows.Forms.ListBox listBox1;
    private System.Windows.Forms.PictureBox pictureBox1;
}

static class Program {
    [STAThread]
    static void Main() {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }
}

このソースコードを RandTest.cs という名のファイルに保存して、コマンドプロンプトに
path C:\Windows\Microsoft.NET\Framework\v2.0.50727
csc /t:winexe RandTest.cs
と打ち込むと RandTest.exe が作られ、それを実行することで確かめることができる。