为什么初始化新的System.Random不会产生相同的输出?

时间:2019-10-09 07:05:30

标签: c# .net-core

据我记得,这样的代码会连续产生例如5个相同的数字

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

结果:

9 4 0 2 8

因此每个新值都不相同,因此System.Random在最近几个月是否发生了变化?年?

2 个答案:

答案 0 :(得分:5)

该代码从来没有被保证生成相同的数字,尽管通常是从当前系统时间开始初始化种子,这是许多堆栈溢出问题的原因。

无参数构造函数状态的documentation(强调我的意思):

  

默认种子值来自具有有限分辨率的系统时钟。结果,仅在 .NET Framework 上,通过无参数构造函数调用紧密连续创建的不同随机对象将具有相同的默认种子值,因此将产生相同的随机集。数字。 [...] 请注意,此限制不适用于.NET Core。

所以看起来这基本上是在Core中修复的。但这不是总是在Core中修复的。我刚刚创建了一个针对多个框架的测试项目,添加了一些预热代码以避免JIT延迟为下一次迭代更改种子,而且.NET Core 1.x似乎与桌面框架存在相同的问题。我怀疑它是针对2.0修复的。

答案 1 :(得分:1)

该代码开头不好-RNG算法使用前N个值生成具有良好随机性的随机数。

除非提供种子,否则大多数RNG都将使用诸如处理器的滴答计数或时钟之类的东西,它是一个严格递增的数字作为种子。仅此一点就减少了所生成数字的随机性。这就是为什么某些加密工具要求您四处移动鼠标以生成随机初始序列的原因。简单的实现甚至可以使用硬编码的种子。

也就是说,处理器的时钟具有有限的分辨率。如果循环执行得足够快,则几个RNG对象可能最终会收到相同的种子值。

如果尝试使用滴答计数而不是使用Windows API的功能来实现顺序GUID算法,则会遇到相同的问题。

生成正确随机数的正确方法是创建一次Random并重复使用:

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

另一方面,如果您想生成可重现的数字序列以用于例如数据科学实验,则需要明确指定种子:

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

即使有延迟,这也会始终产生1

更新

.NET 4.8 still has this bug。即使在.NET Old 4.8中,代码仍然使用处理器计数:

  public Random() 
    : this(Environment.TickCount) {
  }