0

C#のランダムは十分にランダムではありません

私の目標は、組み込みのランダムまたは暗号化 RNG のいずれかを使用して可能と思われるよりも多くのランダム性を取得することです。


Q: 乱数ジェネレーターからランダム性を高めるにはどうすればよいですか?


まず、Random の 1 つのインスタンスを扱っています。

このルーチンを使用して、ランダムな速度 (XNA Vector2) をオブジェクトに割り当てています。

    public static double d2r = Math.PI / 180f;
    public static Vector2 rndV2gtHalf(float scaleFactor)
    {
        double dir = random.NextDouble() * 360 * d2r;
        return new Vector2((float)Math.Cos(dir) * scaleFactor, (float)Math.Sin(dir) * scaleFactor);
    }

d2r は度からラジアンへの変換の係数です。テスト目的で、毎秒約 6000 個のオブジェクトを画面に配置しました。それらは、マウスの位置から「ランダムな」方向に進みます。これが結果です

ここに画像の説明を入力

オブジェクトは四方八方に向かいますが、それらは「小刻みに動く」接続された流れのように見えます。ウィグルは、ストリーム自体の「ビン」内のわずかなランダム性の結果であると推測しています。ストリームは中心を中心に回転しないため、小刻みに動いてもカバレッジは完全ではありません。

random.NextDouble の 3 つの使用に依存させることで、ベロシティにさらにランダム性を追加しようとしました。これにより、0 度に近い大きなスワスが発生し、ほとんど空になりました。その空のスワスを除いて、残りの方向は以前と同じように覆われていました.

public static Vector2 rndV2gtHalf(float scaleFactor)
{
    double dir = ((random.NextDouble() + random.NextDouble() + random.NextDouble()) / 3) * 360 * d2r;
    return new Vector2((float)Math.Cos(dir) * scaleFactor, (float)Math.Sin(dir) * scaleFactor);
}

明らかに、これはより多くのランダム性を生み出しません。 ここに画像の説明を入力

そのため、乱数を組み合わせるのが早すぎたのではないかと考え、加算によって数値の確率的性質を根本的に変更していました。それで、ラジアンに変換する前に、それらの束を改造してみました。

public static Vector2 rndV2gtHalf(float scaleFactor)
{
    double dir = random.NextDouble() * 360;
    dir += random.NextDouble() * 360;
    dir += random.NextDouble() * 360;
    dir += random.NextDouble() * 360;
    dir += random.NextDouble() * 360;
    dir += random.NextDouble() * 360;
    dir %= 360;
    dir *= d2r;
    return new Vector2((float)Math.Cos(dir) * scaleFactor, (float)Math.Sin(dir) * scaleFactor);
}

これは、単一の random.NextDouble に依存するよりも優れているようです。 ここに画像の説明を入力

より優れた暗号ランダム num gen があることをどこかで読みましたが、Double-s を生成するネイティブの機能がないように思われるので、それらを無視しようとします。

public static RandomNumberGenerator rng = new RNGCryptoServiceProvider();
public static Vector2 rndV2gtHalf(float scaleFactor)
{
    byte[] tokenData = new byte[2];
    rng.GetBytes(tokenData);
    int myInt = tokenData[1] << 8 + tokenData[0];
    double myDouble = ((double)myInt) / 65536f;
    double dir = myDouble * 360 * d2r;
    return new Vector2((float)Math.Cos(dir) * scaleFactor, (float)Math.Sin(dir) * scaleFactor);
}

これにより、パーティクルがさらに無計画にカバーされます。 ここに画像の説明を入力

たぶん2バイト分のデータが足りないと思い、4つに増やしました。

        byte[] tokenData = new byte[4];
        rng.GetBytes(tokenData);
        int myInt = tokenData[3]<<24 + tokenData[2]<<16 + tokenData[1]<<8 + tokenData[0];
        double myDouble = ((double)myInt) / 4294967296;

これでは改善されません。 ここに画像の説明を入力

わかった。だから多分それは私がsin/cosを使っている方法と関係がある. fn を次のように作り直します。

public static Vector2 rndV2gtHalf(float scaleFactor)
{
    return scaleFactor * 
           new Vector2((float)(random.NextDouble() - 0.5f), (float)(random.NextDouble() - 0.5f));
}

これにより、画面の端まで成長する四角いエッジのストリームのフィールドが生成されることに加えて、sin/cos の試みよりもランダムに見えなくなります。 ここに画像の説明を入力

私が試みたものは何もうまくいかないようです。それで、私の質問を繰り返しますが、どうすれば乱数ジェネレーターからより多くのランダム性を得ることができますか?

4

5 に答える 5

1

気まぐれで、私は scaleFactor で遊んで、rdm double を追加および削除することにしました。渡された scaleFactor は常に 2.5 です。

public static double TwoPi = Math.PI * 2f;
public static Vector2 rndV2gtHalf(float scaleFactor)
{
    scaleFactor = (float)(scaleFactor + random.NextDouble() - random.NextDouble());
    double dir = random.NextDouble() * TwoPi;
    return new Vector2  (
                            (float)Math.Cos(dir) * scaleFactor,
                            (float)Math.Sin(dir) * scaleFactor
                        );
}

驚いたことに、これにより、これまでよりもはるかに「完全な」粒子分散が生成されました。 ここに画像の説明を入力

どちらかが良ければ多いほうがいいと思い、さらに4回繰り返しましたが違いがわかりませんでした。

ここに画像の説明を入力

0 度と 180 度の近くにはまだ希望以上の空白がありますが、これは以前よりも大幅に改善されています。

理由はよくわかりませんが、これで問題は効果的に解決されます。

于 2013-11-06T05:24:03.493 に答える
0

フォン ノイマン エクストラクタと呼ばれるものがあります。弱くバイアスされたビット ソースがある場合は、連続する (重複しない) ペアを取得します。それらが同一である場合は、次のペアに進みます。それらが異なる場合は、最初のものを返します。これにより、多くのビットを「浪費」する代わりに、入力ストリームのランダム性が向上します。

ただし、前述のように、問題は別の場所にあるようです。

于 2013-11-06T11:15:26.083 に答える
0

問題は私の乱数ルーチンではなく、別の場所にあると言ったすべての人に、私の他のコードのいくつかにバグがあったにちがいない、、、あなたは正しかった.

長方形の場所に/から自分の場所を保存/取得することで、そのコンポーネントを整数に切り捨てていたことを発見しました。

振り返ってみると、scaleFactor をいじり、同じ範囲の乱数を追加してから削除することで、数値の範囲を拡大していることにすでに気付いていることが手がかりになるはずでした。より多くの範囲をカバーするために切り捨てられたときに、実際には十分に異なる数値を持つようにします。

これに対する答えを誰が信じているか正確にはわかりません。

私の最終的なルーチン コードは次のようになりました。

public static double TwoPi = Math.PI * 2f;
public static Vector2 rndV2gtHalf(float scaleFactor)
{
    double dir = random.NextDouble() * TwoPi;
    return new Vector2(
                            (float)Math.Cos(dir) * scaleFactor,
                            (float)Math.Sin(dir) * scaleFactor
                        );
}

視覚的な結果は次のとおりです。 ここに画像の説明を入力

于 2013-11-06T15:08:55.147 に答える
0

これは、私が定期的に使用する RNGCryptoServiceProvider に基づいたクラスです。元の著者は、すばらしい記事とクラスを書いた Jim Mischelです。

私の確率論のコースは昔からありますが、この「スター」パターンは、均一に分散された乱数発生器を使用する場合に典型的なものではありませんか? 私が覚えている限り、真に一様な乱数発生器には好みがないため、どの数値が描画される可能性も等しくなります。これは、粒子の方向も円の周りに優先せずに均一に分布していることを意味し、最終的には星になりますよね?

/*
 * Adapted from Jim Mischel 
 * http://www.informit.com/guides/content.aspx?g=dotnet&seqNum=775
*/
public class RandomEx
{
  private const int BufferSize = 1024;  // must be a multiple of 4
  private byte[] RandomBuffer;
  private int BufferOffset;
  private RNGCryptoServiceProvider rng;

  public RandomEx()
  {
    RandomBuffer = new byte[BufferSize];
    rng = new RNGCryptoServiceProvider();
    BufferOffset = RandomBuffer.Length;
  }

  private void FillBuffer()
  {
    rng.GetBytes(RandomBuffer);
    BufferOffset = 0;
  }

  public int Next()
  {
    if (BufferOffset >= RandomBuffer.Length)
    {
      FillBuffer();
    }
    int val = BitConverter.ToInt32(RandomBuffer, BufferOffset) & 0x7fffffff;
    BufferOffset += sizeof(int);
    return val;
  }

  public double NextDouble()
  {
    int val = Next();
    return (double)val / int.MaxValue;
  }
}
于 2013-11-06T02:06:38.420 に答える
0

私はあなたのコードをテストしましたが、私にとっては完璧に機能します。残念ながら、私はまだ画像を投稿することを許可されていません。

そうは言っても、おそらく、望ましくない動作を引き起こすバグがどこかにあるでしょう。それを確認するには、別の方法でランダムな方向を選択し、問題が解決するかどうかを確認します。

    public Vector2 getRandomVelocity(float scaleFactor, Random random)
    {
        float angle = MathHelper.ToRadians(random.Next(361));

        return new Vector2((float)Math.Cos(angle) * scaleFactor, (float)Math.Sin(angle) * scaleFactor);
    }
于 2013-11-06T14:13:10.033 に答える