0

C# 的 Random 不够随机

我的目标是获得比使用内置 Random 或加密 RNG 可能实现的更多随机性。


问:如何从随机数生成器中获得更多随机性?


首先,我正在处理 Random 的单个实例。

我正在使用此例程为对象分配随机速度(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);
}

这导致粒子的覆盖更加随意。 在此处输入图像描述

考虑到两个字节的数据可能不够,我将其增加到 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,添加和删除 rndm 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

我已经对您的代码进行了测试,它对我来说非常有效。不幸的是,我还不允许发布图像。

也就是说,其他地方可能存在导致不良行为的错误。为了验证这一点,您可以采用不同的方法来选择随机方向并检查它是否解决了问题:

    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 回答
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

有一种叫做冯诺依曼提取器的东西:如果你有一个弱偏向的比特源,取连续的(不重叠的)对;如果它们相同,则转到下一对;如果它们不同,则返回第一个。这以“浪费”大量比特为代价提高了输入流的随机性。

但是,如前所述,您的问题似乎位于其他地方。

于 2013-11-06T11:15:26.083 回答