C#随机数经常超过预期

时间:2011-11-14 17:12:08

标签: c# asp.net random

我们有这个随机数生成器:

Random rnd = new Random();
bool PiratePrincess = rnd.Next(1, 5000) == 1;

每个页面视图都会调用此方法。变量为True的概率应为1/5000。但是,在大约15,000次页面浏览量中,这已经是True大约20次!

有人可以解释为什么会这样,以及如何防止这种情况大约是1/5000倍?完全随机的并不重要。

编辑:这可以解决这个问题吗?

Random rnd = new Random();
bool PiratePrincess = (rnd.Next() + ThisUser.UserID + ThisUser.LastVisit.Ticks + DateTime.Now.Ticks) % 5000 == 1;

6 个答案:

答案 0 :(得分:8)

这些综合浏览量的速度有多快? new Random将根据当前时间进行初始化,因此许多重叠请求将获得相同的随机种子。根据远程IP地址随机化种子和当前时间以获得更多唯一性。

也就是说,可以将硬币翻转20次并且每次都能获得头部。这是一个合理的随机结果。

编辑:这样做

var r = new Random(
    Convert.ToInt32(
        (ThisUser.UserID ^ ThisUser.LastVisit.Ticks ^ DateTime.Now.Ticks) & 0xFFFFFFFF)
    );

var isPiratePrincess = (r.Next (5000) == 42);

答案 1 :(得分:3)

您只需要实例化Random的单个实例,因此:

public class Widget
{
  private static rngInstance = new Random() ;

  public bool IsPiratePrincess()
  {
    bool isTrue = rngInstance.Next(1, 5000) == 1 ;
    return isTrue ;
  }

}

伪随机数生成器实现一系列。每次实例化一个新实例时,它都会根据(除其他外)当前时间进行种子设定,并开始一个新的系列。

如果在每次调用时实例化一个新实例,并且实例化足够频繁,您可能会在生成的伪随机值流中看到相似之处,因为初始种子值可能彼此接近。

编辑注意:由于System.Random不是线程安全的,因此您可能希望以一种使其线程安全的方式进行换行。这个类(来自Getting Random Numbers in Thread-Safe Way)将起到作用。它使用每线程静态字段并为每个线程实例化一个RNG实例:

public static class RandomGen2 
{ 
  private static Random _global = new Random(); 
  [ThreadStatic] 
  private static Random _local;

  public static int Next() 
  { 
      Random inst = _local; 
      if (inst == null) 
      { 
        int seed; 
        lock (_global) seed = _global.Next(); 
        _local = inst = new Random(seed); 
      } 
    return inst.Next(); 
  } 
}

我有点怀疑用另一个RNG的输出为每个RNG实例播种。这让我有可能对结果产生偏见。我认为使用默认构造函数可能会更好。

另一种方法是锁定对单个RNG实例的每次访问:

public static class RandomGen1 
{ 
  private static Random _inst = new Random();

  public static int Next() 
  { 
    lock (_inst) return _inst.Next(); 
  } 
}

但是这有一些性能问题(瓶颈,加上每次调用锁定的开销)。

答案 2 :(得分:1)

每次生成数字时,您都会使用新种子创建一个新的随机数生成器。您获得的值的分布将更多地与种子有关,而不是与使用的算法的分布特征有关。

我不确定实现更均匀分布的最佳方法是什么。如果您的流量足够低,您可以在页面上使用点击计数器检查可分性5000,但如果您尝试扩展它,这种方法很快会遇到争用问题。

答案 3 :(得分:1)

如果问题确实是种子(包括我自己怀疑的很多人),你可以坚持使用Random()实例(这会导致高容量下的并发问题,但每天点击约15,000次就可以了),或者你可以为种子引入更多的熵。

如果这是针对您不希望确定的人打破伪随机特征的应用程序,您应该查看在您的服务器上生成良好种子的软件或硬件(扑克网站通常使用硬件熵生成器)。

如果您只是想要一个好的发行版并且不希望人们尝试破解您的解决方案,请考虑混合各种熵源(当前时间戳,用户的用户代理字符串的哈希值,IPv4地址或哈希值) IPv6地址等。)。

更新:您提到您也拥有用户ID。哈希表示熵以及上面提到的一个或多个项目,尤其是当前时间戳的刻度。

答案 4 :(得分:1)

如果完全避免Random,则可以获得1/5000。

vals = Enumerable.Repeat(false, 4999).ToList();
vals.Add(true);

// this or an in-memory shuffle if that is a concern
isPiratePrincess = vals.OrderBy(v => Guid.NewGuid()).ToList();

// remove values as it's queried & reset when empty.

在这里使用Random的问题是你可能处于15K中有20个被合法命中的状态。在接下来的几千次迭代中,你会遇到一个让你回归到预期平均值的冷酷。

答案 5 :(得分:0)

随机数生成器使用当前时间作为随机数生成器的种子。所以,如果两个人同时进来,他们理论上可以拥有相同的种子。如果您希望您的号码彼此唯一,您可能希望对该页面的所有实例使用相同的随机数生成器。