1

As far as I remember code like this would produce e.g 5 the same numbers in a row

for (int i = 0; i < 5; i++)
{
    var rnd = new Random();
    Console.WriteLine(rnd.Next(10));
}

Result:

9 4 0 2 8

So every new value is different, so was System.Random changed in last months? year?

Joelty
  • 1,751
  • 5
  • 22
  • 64
  • That's the point of a random-number generator. And no, this code should *NEVER* produce the same sequence. This hasn't changed since 2002. That's true of *all* RNGs by the way, in R, Matlab, Python – Panagiotis Kanavos Oct 09 '19 at 07:07
  • @PanagiotisKanavos https://stackoverflow.com/a/19758777 – Joelty Oct 09 '19 at 07:08
  • Perhaps your *real* question is how to generate *reproducible* sequences for an experiment? Specify your *own* seed, don't let the RNG pick its own. That's shown in all data science tutorials. The default for all well-written RNGs is to use a changing value like a processor' tick count. – Panagiotis Kanavos Oct 09 '19 at 07:09
  • No, it isn't my question. I've seen multiple times where people would create new Random in loop and would be shocked that output is not random at all, just check link. – Joelty Oct 09 '19 at 07:10
  • [Can't reproduce](https://dotnetfiddle.net/LwiVzg). Can your provide a [mcve]? – Sweeper Oct 09 '19 at 07:11
  • @Sweeper Change compilator from Rosyln 3.0 to .NET Framework 4.7.2 :) – Joelty Oct 09 '19 at 07:12
  • @Joelty they were already using bad code in that case and the repeated numbers were a result of that bad code. The RNG should *not* be rebuilt each time. RNG algorithms depend on the previous N values to generate a new one with good randomness characteristics – Panagiotis Kanavos Oct 09 '19 at 07:12

2 Answers2

6

That code was never guaranteed to produce the same numbers, although it typically would as the seed was initialized from the current system time, and this has been the cause of many Stack Overflow questions.

The documentation for the parameterless constructor states (emphasis mine):

The default seed value is derived from the system clock, which has finite resolution. As a result, on the .NET Framework only, different Random objects that are created in close succession by a call to the parameterless constructor will have identical default seed values and, therefore, will produce identical sets of random numbers. [... ] Note that this limitation does not apply to .NET Core.

So it looks like this was basically fixed in Core. But it wasn't always fixed in Core. I've just created a test project targeting multiple frameworks, adding a bit of warmup code to avoid JIT delays changing the seed for the next iteration, and it looks like .NET Core 1.x had the same issue as the desktop framework. It was fixed for 2.0, I suspect.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • `Note that this limitation does not apply to .NET Core.` Thanks, it expalins a lot. – Joelty Oct 09 '19 at 07:11
  • And finally we can have a look at the source itself https://github.com/dotnet/corefx/blob/master/src/Common/src/CoreLib/System/Random.cs#124 - the initial seed is generated from a global random instance – Sir Rufo Oct 09 '19 at 07:13
  • @SirRufo: Yup - unfortunately the history doesn't go back far enough to easily see when this was fixed. (I can't be bothered to follow the file moving around ;) – Jon Skeet Oct 09 '19 at 07:16
1

That code is bad to begin with - RNG algorithms use the previous N values to generate random numbers with good randomness characteristics.

Unless a seed is provided, most RNGs will use something like the processors' tick count or clock, which is a strictly incrementing number as the seed. This alone reduces the randomness of the generated numbers. That's why some cryptographic tools require you to move the mouse around to generate a random initial sequence. Simplistic implementations may even use a hard-coded seed.

That said, the processor's clock has a finite resolution. If the loop executes quickly enough, several RNG objects may end up receiving the same seed value.

You'll get into the same problem if you try to implement a sequential GUID algorithm using the tick count instead of using Windows API's functions.

The correct way to generate good random numbers is to create Random once and reuse it :

var rnd = new Random();
for (int i = 0; i < 5; i++)
{
    Console.WriteLine(rnd.Next(10));
}

If on the other hand you want to generate reproducible number sequences for use eg in data science experiments, you need to specify the seed explicitly :

for (int i = 0; i < 5; i++)
{
    Thread.Sleep(100);
    var rnd = new Random(1023);
    Console.WriteLine(rnd.Next(10));
}

This produces 1 consistently, even though there's a delay

Update

.NET 4.8 still has this bug. The code still uses the processor count even in .NET Old 4.8 :

  public Random() 
    : this(Environment.TickCount) {
  }
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236