【问题标题】:Multiple random numbers are the same [duplicate]多个随机数相同[重复]
【发布时间】:2013-01-18 09:26:15
【问题描述】:

可能重复:
Random number generator only generating one random number

一个初学者的问题。我有一个非常简单的程序来画一条线,我想随机化位置,但是每次我创建一个新的 Random 实例时,它都会返回相同的值。问题出在哪里?谢谢。

private void Draw()
{
    Random random1 = new Random();
    int randomNumber1 = random1.Next(0, 300);
    Random random2 = new Random();
    int randomNumber2 = random2.Next(0, 300);
    Random random3 = new Random();
    int randomNumber3 = random3.Next(0, 300);
    Random random4 = new Random();
    int randomNumber4 = random4.Next(0, 300);
    System.Drawing.Graphics g = this.CreateGraphics();
    Pen green = new Pen(Color.Green, 5);
    g.DrawLine(green, new Point(randomNumber1, randomNumber2), 
                      new Point(randomNumber3, randomNumber4));
}

private void btndraw1_Click(object sender, EventArgs e)
{
    Draw();
}

【问题讨论】:

    标签: c# random


    【解决方案1】:

    只需使用相同的实例:

    Random random = new Random();
    int randomNumber1 = random.Next(0, 300);
    int randomNumber2 = random.Next(0, 300);
    //...
    

    编程中的随机数并不是真正随机的;它们基于一些独特的seed,这些seed 被采用和操纵以生成看似随机数集的内容。使用相同的种子将产生相同的一组数字。

    Random 类的默认构造函数使用自系统启动以来经过的毫秒数作为种子,因此实际发生的情况是使用了相同的种子。

    真的没有理由多次创建Random 实例;单个实例将在每次执行代码时生成一组随机数字。

    为了证明我上面关于默认种子的说法,我使用了反射:

    // System.Random
    /// <summary>Initializes a new instance of the <see cref="T:System.Random" /> class, using a time-dependent default seed value.</summary>
    public Random() : this(Environment.TickCount)
    {
    }
    

    还有Environment.TickCount

    // System.Environment
    /// <summary>Gets the number of milliseconds elapsed since the system started.</summary>
    /// <returns>A 32-bit signed integer containing the amount of time in milliseconds that has passed since the last time the computer was started.</returns>
    /// <filterpriority>1</filterpriority>
    public static extern int TickCount
    {
        [SecuritySafeCritical]
        [MethodImpl(MethodImplOptions.InternalCall)]
        get;
    }
    

    【讨论】:

    • 只是...你怎么知道Random使用的是Unix纪元? documentation 仅声明它 derived from the system clock 但从未提及实际实现。
    • @Alvin 好点!这正是我一直认为,在我看来是最合理的事情。使用 ILSpy 我发现我完全错了,真正的种子是系统启动后的毫秒数。
    【解决方案2】:

    发生这种情况的原因是每次您执行新的Random 时都会使用时钟对其进行初始化。因此,在一个紧密的循环中(或一个接一个的多次调用),您会多次获得相同的值,因为所有这些随机变量都是用相同的种子初始化的。

    要解决这个问题:只创建一个随机变量,最好在你的函数之外,并且只使用那个实例。

    Random random1 = new Random();
    private void Draw()
    {
        int randomNumber1 = random1.Next(0, 300);
        int randomNumber2 = random1.Next(0, 300);
        int randomNumber3 = random1.Next(0, 300);
        int randomNumber4 = random1.Next(0, 300);
        System.Drawing.Graphics g = this.CreateGraphics();
        Pen green = new Pen(Color.Green, 5);
        g.DrawLine(green, new Point(randomNumber1, randomNumber2), new Point(randomNumber3, randomNumber4));
    }
    

    【讨论】:

    • +1 表示方法外。
    【解决方案3】:

    您只需要一个 Random 类的实例。

    private void Draw()
        {
            Random random1 = new Random();
            int randomNumber1 = random1.Next(0, 300);
    
            int randomNumber2 = random1.Next(0, 300);
    
            int randomNumber3 = random1.Next(0, 300);
    
            int randomNumber4 = random1.Next(0, 300);
    
            System.Drawing.Graphics g = this.CreateGraphics();
            Pen green = new Pen(Color.Green, 5);
            g.DrawLine(green, new Point(randomNumber1, randomNumber2), new Point(randomNumber3, randomNumber4));
        }
    
    
        private void btndraw1_Click(object sender, EventArgs e)
        {
            Draw();
        }
    

    【讨论】:

      【解决方案4】:
          private static readonly Random Random1 = new Random();
      
          private void Draw()
          {
      
              int randomNumber1 = Random1.Next(0, 300);
              int randomNumber2 = Random1.Next(0, 300);
              int randomNumber3 = Random1.Next(0, 300);
              int randomNumber4 = Random1.Next(0, 300);
              System.Drawing.Graphics g = this.CreateGraphics();
              Pen green = new Pen(Color.Green, 5);
              g.DrawLine(green, new Point(randomNumber1, randomNumber2), new Point(randomNumber3, randomNumber4));
          }
      
      
          private void btndraw1_Click(object sender, EventArgs e)
          {
              Draw();
          }
      

      【讨论】:

        【解决方案5】:

        您不应该为每个号码创建一个新的Random 对象。相反,使用相同的对象:

        Random r = new Random();
        
        private void Draw()
        {
            // Create 4 random numbers
            int[] numbers = Enumerable.Range(0, 4).Select(x => r.Next(0, 300)).ToArray();
        
            System.Drawing.Graphics g = this.CreateGraphics();
            Pen green = new Pen(Color.Green, 5);
            g.DrawLine(green, new Point(numbers[0], numbers[1]),
                              new Point(numbers[2], numbers[3]));
        }
        

        【讨论】:

          【解决方案6】:

          .Net 的随机类需要一个种子值,您可以使用日期值作为种子,它会起作用。

          private void Draw()
              {
                  Random random1 = new Random(unchecked((int)DateTime.Now.Ticks << (int)100));
                  int randomNumber1 = random1.Next(0, 300);
                  Random random2 = new Random(unchecked((int)DateTime.Now.Ticks << (int)200));
                  int randomNumber2 = random2.Next(0, 300);
                  Random random3 = new Random(unchecked((int)DateTime.Now.Ticks << (int)300));
                  int randomNumber3 = random3.Next(0, 300);
                  Random random4 = new Random(unchecked((int)DateTime.Now.Ticks << (int)400));
                  int randomNumber4 = random4.Next(0, 300);
                  System.Drawing.Graphics g = this.CreateGraphics();
                  Pen green = new Pen(Color.Green, 5);
                  g.DrawLine(green, new Point(randomNumber1, randomNumber2), new Point(randomNumber3, randomNumber4));
              }
          
          
          private void btndraw1_Click(object sender, EventArgs e)
          {
              Draw();
          }
          

          【讨论】:

          • 为什么需要像你使用的那样的种子值? Random() 默认使用类似的东西。
          • 确实如此,但它们总是倾向于生成相同的发送数字,添加唯​​一的种子号会更安全,这将保证为不同的随机类对象生成随机数。
          • @Nikshep 这仍然是一种解决方法。如果您有不同的函数生成Randoms 怎么办?你会记账吗?如果是这样,为什么不使用静态随机数呢?而且您意识到将Ticks 向左移动 100 到 400 个位置是荒谬的,对吧?
          • @Antonijn 这是真的,但我只是想迭代 .Net 的 Random 类的行为。正如我发现所问的问题是针对特定行为还是希望解决第一个问题中的代码问题。
          • 首先:如果100 已经是int,你为什么要做(int)100?其次:DateTime.Tickslong,所以它是 64 位大的。在这种情况下执行Ticks &lt;&lt; 64 将返回与Ticks &lt;&lt; 0Ticks 相同的结果;所以Ticks &lt;&lt; 100Ticks &lt;&lt; 36Ticks &lt;&lt; 200 == Ticks &lt;&lt; 4Ticks &lt;&lt; 300 == Ticks &lt;&lt; 40Ticks &lt;&lt; 400 == Ticks &lt;&lt; 12 相同。第三:randomNumber1 = random.Next(0, 300); randomNumber2 = random.Next(0, 300) 绝对比创建大量 Random 对象更容易和更好。难怪你会得到所有这些反对意见。
          【解决方案7】:

          随机数生成器 (RNG) 实际上并不生成随机数。相反,它使用一种算法来定义一系列看似随机的数字。此序列取决于创建 RNG 时通过所述算法运行的seed

          默认情况下,RNG 是使用系统时钟作为种子创建的,因为每次程序运行时时钟通常都会发生变化,因此很难预测“随机”序列。

          在您的情况下,时钟很可能在创建一个随机对象和另一个对象之间没有改变;可能是由于 CPU 内部的指令重新排序。

          正如 Blachshma 所说,最好只创建一个随机对象并仅使用它。

          public static Random MyRNG = new Random(); // create a single static random object, that you can use across all classes
          private void Draw()
          {
              randomNumber1 = MyRNG.Next(0, 300);
              randomNumber2 = MyRNG.Next(0, 300);
              // and so forth
          }
          

          请记住,System.Random 的任何实例都不能保证是线程安全的,这意味着如果您计划让多个线程共享同一个随机对象,您必须锁定它。

          lock (MyRNG)
          {
              randomNumber = MyRNG.Next(0, 300);
          }
          

          如果不这样做可能会破坏您的随机对象,从而导致后续调用仅返回 0。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2013-07-29
            • 1970-01-01
            • 2012-01-16
            • 1970-01-01
            • 2019-05-21
            • 2020-02-20
            • 2012-10-22
            相关资源
            最近更新 更多