【问题标题】:C# Random Number Not Random EnoughC# 随机数不够随机
【发布时间】:2013-11-06 01:42:16
【问题描述】:

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 更好。

我在某处读到有一个更好的加密随机数 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 尝试那么随机。

我尝试的任何方法似乎都不起作用。那么,重复我的问题,我怎样才能从随机数生成器中获得更多随机性?

【问题讨论】:

  • 如果您以数字而不是图片的形式表达您的担忧,也许会有所帮助。
  • 您基本上是在随机进行(哈哈),而没有真正从逻辑上考虑问题。例如,考虑将三个随机数相加并取其平均值的想法“更随机”——即更少均匀随机。当你一遍又一遍地滚动一个骰子时,你会得到一个到六个均匀分布的骰子。当你掷两个骰子并取平均值时,你会得到 3.5 17% 的时间,但只有 3% 的时间得到 1。您使分布得到的 less 的总和越多;这就是中心极限定理。
  • 鉴于当您更改随机算法时显示的形状几乎没有变化,所以问题可能首先不在随机算法中。您可能在代码中的其他地方存在未向我们展示的错误。
  • Eric:我同意,无论是关于我的挥舞,还是关于平均的影响。尽管如此,它还是添加然后删除了一个 dbl(再次更改范围),这给了我更多随机结果。最奇怪的部分是它是在 scaleFactor 上,而不是在角度上。那么,关于您的评论,实际应用是什么——应用以使 RNG 更加随机的逻辑思维是什么?关于其他地方的错误,除了初始速度的设置外,在每次更新时,都会将速度添加到位置。就是这样。
  • 但如果你不相信我,这是我使用的代码(抱歉,由于在 cmets 中未格式化): public override void move(ImageObj obj, GameTime gameTime) { obj.Location += 对象速度; if ( (obj.RectScaled.Right G.screenSize.X) | (obj.RectScaled.Bottom G.screenSize.Y) ) { obj.alive = false; } }

标签: c# random xna


【解决方案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 度附近的空虚度仍然比我想要的要多,但这比我以前的情况有了很大的改进。

我不太明白为什么,但这有效地解决了我的问题。

【讨论】:

    【解决方案2】:

    这是一个基于我经常使用的 RNGCryptoServiceProvider 的改编类。原作者是 Jim Mischel,他写了一个很棒的article 和班级。

    我的随机学课程倒退了一步,但在使用均匀分布的随机发生器时,这种“星形”模式不是很典型吗?据我所知,一个真正统一的随机生成器不应该有任何偏好,因此抽取任何数字的可能性都是相等的。这意味着你的粒子的方向也均匀分布在一个圆圈周围,没有任何偏好,最终成为一颗星星,对吧?

    /*
     * 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;
      }
    }
    

    【讨论】:

    • 我实现了您的解决方案,结果与我的第一张照片几乎相同:28 条径向摆动的流,它们之间有很多空白空间。从好的方面来说,我确实学会了如何使用 BitConverter。但我仍在寻找更多的随机性。
    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

      我已经对您的代码进行了测试,它非常适合我。不幸的是,我还不能发布图片。

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

          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);
          }
      

      【讨论】:

        【解决方案5】:

        对于所有说问题出在其他地方的人,而不是在我的随机数例程中,我的其他代码中一定存在错误,,,你是对的。

        我发现通过在矩形位置中/从矩形位置中存储/检索我的位置,我一直在将其组件截断为整数。

        回想起来,它应该是一个线索,通过使用 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
                                );
        }
        

        这是视觉结果:

        【讨论】:

          猜你喜欢
          • 2011-11-07
          • 1970-01-01
          • 1970-01-01
          • 2014-04-27
          • 2018-05-15
          • 2015-09-10
          • 1970-01-01
          • 2011-07-02
          • 2017-04-08
          相关资源
          最近更新 更多