为什么 Random() 不是真正的随机?

7

可能是重复问题:
为什么这个随机数生成器不随机?

我有这个测试程序:

static void Main(string[] args)
{
    var randomNumbers = new Dictionary<int, int>();
    foreach (var s in Enumerable.Range(1, 500))
    {
        var rand = Rand5();
        if (!randomNumbers.ContainsKey(rand))
            randomNumbers.Add(rand, 1);
        else
            randomNumbers[rand] += 1;
    }

    randomNumbers
        .ToList()
        .ForEach(x => Console.WriteLine("{0}: {1}", x.Key, x.Value));
    Console.ReadLine();
}

static int Rand5()
{
    System.Threading.Thread.Sleep(1);
    return new Random().Next(1, 6);
}

如果我注释掉System.Threading.Thread.Sleep(1);,那么我会得到:
5: 500

但是如果我取消注释那一行,就会得到随机数。

2: 87
4: 94
1: 116
5: 108
3: 95

为什么代码行很重要?谢谢!

我猜标记这样的问题为重复可能是徒劳无功的,因为大约三分之一的所有随机标记的问题都是相同的问题。 - Joey
6个回答

11
Random类型默认根据当前系统时间进行种子生成,但该时间粒度是有限的。如果快速连续地调用new Random().Next(1, 6),则会构造许多具有相同种子值的Random对象,从而产生相同的结果。通过调用Thread.Sleep(1),可以解决这个问题,它可以简单地将构造间隔开一段时间,增加不同种子值的概率。
你需要保留一个特定的Random对象,以便在每次调用之间使用。
var randomNumbers = new Dictionary<int, int>();
var random = new Random(); // Re-use this, don't keep creating new ones.
foreach (var s in Enumerable.Range(1, 500))
{
    var rand = random.Next(1, 6);
    // ...

10

正如其他人所说,new Random() 会将随机数生成器的种子设置为当前系统时间。

我有一篇文章详细描述了这一点,并提供了解决问题的方法,你可能会觉得有用。基本上,你需要多次使用相同的 Random 实例 - 但请注意它不是线程安全的。


感谢您详细解答我的问题。顺便说一下,我正在阅读您的《C#深入剖析2》 :) - bla
哇,这是一篇很棒的文章。我比10分钟前更好地理解了这个问题。 - Justin Morgan
这就是为什么我希望 System.Random 是一个静态单例,延迟初始化的原因。 - Jonathon Reinhart

3
因为它使用时钟作为生成数字的种子,当你以这种方式生成随机数时,会得到相同的数字。

2
随机数生成器部分基于系统时钟,而C#生成随机数的速度太快了...

1
如果您不种子随机数,那么您会得到与 Random 是伪随机生成器相同的数字。
通过使用 Thread.Sleep(1),您允许计时器前进并生成新的自动生成种子。
一种“修复”的方法是创建 1 个随机对象并重用它(就像其他一些人也回答的那样),或者使用不同的随机生成器。
更多信息请参见 http://msdn.microsoft.com/en-us/library/ctssatww.aspx

0

你使用的任何随机数生成器都是伪随机数。它总是具有预定义的种子值,适用于测试,但不适用于实现真正随机性的特征。

你应该使用准随机数序列来生成随机数,或者更好的是使用马尔科夫链来生成最佳随机数。如果你计划使用其中一个随机函数,你将无法获得真正的随机性。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接