使用C#生成随机数。

11
我正在考虑生成1到500万之间的随机数。这个过程不一定要很快(尽管如果快一些会很好),但必须尽可能随机(我知道没有什么是完全随机的)。我有多种用于种子的数据源。
我不确定.NET Random类是否足够好。
这将用于选择中奖彩票。

5
“尽可能随机”的定义需要清晰而明确。要获得实际上“尽可能随机”的信号,你需要一个熵源——种子——它包含的比从该种子生成的随机项更多的熵位。因此,如果你已经有多个高熵的数据源用于生成这个种子,那么你已经完成了一半。只需从你的熵源中提取24位,就可以得到一个介于0和1600万之间的数字,其在给定熵源的情况下是“尽可能随机”的。 - Eric Lippert
2
此外,如果您描述一下您将使用这个随机数的用途,那将非常有帮助。 - Eric Lippert
3
关于你的更新:现在你不再处于纯数学和技术领域,而是进入法规领域。大多数读者都不是熟悉选择赢家设备应具备哪些特征的律师。我建议在继续追求技术解决方案之前请教律师和统计学家。肯定没有一个 简单的“伪随机”解决方案会被接受;使用伪随机数生成器可以轻易地推出下一个中奖号码的值。 - Eric Lippert
8
你无法想象它如何被解决并不意味着没有人能够看清楚如何解决。伪随机数发生器被称为“伪随机”是因为它们不是随机的。它们是可预测的;如果你有关于它们过去输出的信息,你就可以计算出它们未来的输出可能性。此外,一个随机数发生器不仅需要是不可预测的,还必须是无偏的 - Eric Lippert
1
@CodeInChaos:没错,这就是为什么我说没有简单的伪随机解决方案——比如调用Random.Next——会奏效。一个具有加密强度的随机解决方案可能足够随机以防止欺诈,但仍然可能不符合法律要求。 - Eric Lippert
显示剩余2条评论
8个回答

17

使用 System.Random 类可能已经足够好了:

伪随机数是从有限数字集中以等概率选择的。选择的数字并不完全随机,因为采用了确定的数学算法来选择它们,但对于实际目的来说它们已足够随机。当前的 Random 类实现基于 Donald E. Knuth 的差分随机数生成器算法。更多信息请参见 D. E. Knuth 所著《计算机程序设计艺术第二卷:半数值算法》(Addison-Wesley, Reading, MA, 第二版,1981 年)。

唯一需要注意的是不要太频繁地重复使用相同的种子:

如果反复使用相同的种子,则会生成相同的一系列数字。产生不同序列的一种方法是使种子值与时间相关,从而在每个新的 Random 实例中产生不同的序列。


1
请查看此处 http://connect.microsoft.com/VisualStudio/feedback/details/634761/system-random-serious-bug 它编码不正确,可能不够好。 - evolvedmicrobe
@evolvedmicrobe:在修复了System.Random的特定错误版本上运行了一堆统计测试后,我并不清楚是否还有其他关于RNG应该更加令人担忧的问题。当然,它有几个不同的问题:https://github.com/dotnet/corefx/issues/23298 - fuglede

7
如果需要加密随机数,请使用 System.Security.Cryptography.RNGCryptoServiceProvider 类或使用 RandomNumberGenerator.Create() 工厂方法创建默认配置的随机数生成器。

3
RNGCryptServiceProvider 用于填充字节数组,因此您需要将其转换回 int 并检查它是否在正确的范围内。 - zebrabox
1
+1 给 @zebrabox 的评论,另外:如果生成的值超出范围,请不要使用模运算符(%),因为这可能会导致某些值偏差。请循环生成另一个值。 - devstuff
@devstuff 这也是不使用 System.Random 的又一个原因,因为它们的 Next(...) 实现存在偏差(它们似乎不使用 %,但数字并不是等可能的)。 - CodesInChaos
@CodeInChaos:但是System.Random不能用于加密数字,这正是我的答案所涉及的内容。 - Steven
当然。但即使对于非加密数字也存在缺陷,因为它具有与@devstuff提到的相似的缺陷。 - CodesInChaos

5

请查看Jon Skeet的博客文章重新审视随机性,这是如何使用随机性的非常好的评论:

重新审视随机性
几乎每一个包含“随机”和“重复”的Stack Overflow问题都有相同的基本答案。这是.NET、Java和其他平台中最常见的“陷阱”之一:如果不指定种子创建新的随机数生成器,则会依赖于当前的时间瞬间。计算机测量的当前时间与您可以创建和使用随机数生成器的频率相比,并没有经常改变-因此,重复创建Random实例并仅使用一次的代码将最终显示出很多重复。

更多...


"更多..."链接现在失效了。 - Tyler Forsythe

5

最近我读了一篇非常好的文章,介绍了不同类型的伪随机数生成器(PRNGs)在多个随机性测试中的表现。但很遗憾,我现在找不到它了。总的来说,几乎所有流行编程语言中默认的随机数生成器都很幼稚,具有相当大的偏差。

另一个答案已经提到,无论算法有多复杂,没有任何PRNG可以用于加密应用。这是正确的。既然你提到这将用于“选择获胜彩票”,那么我们暂且不考虑这个问题。

.NET System.Random类使用的Knuth算法主要针对速度进行了优化,而不是随机分布。对于许多目的来说,它的随机性已经足够好了,大多数应用程序也没有离开这个范围。但在游戏和统计模拟领域,大多数人似乎认为它是一个糟糕的选择。它比旧库中默认的线性同余发生器(LCGs)更好,但你仍然不想将其用于类似于彩票之类的东西。

不要被认为只需使用加密源就可以愚弄,因为加密随机数生成器的问题在于它们填充字节流,但将其转换为介于xy之间的单个随机整数需要进行一些模算术(或四舍五入-结果相同)。如果您的随机范围不能完全均匀地分成字节长度定义的任何2的幂,则最终会得到较低数字的偏差。生成的数据具有高熵,但您的结果将是有偏的。

作为一个简单的例子,假设您从1到10中获得了一个“完美”的随机数,现在您想将其转换为介于1和7之间的随机数。怎么做?简单地计算result % 7会严重偏向于数字1-3。使用加密随机数生成器时有一些减少偏差的方法,但我试图表达的观点是,加密随机数生成器是专为加密应用而设计的,而在蒙特卡罗模拟中使用其中之一通常不是最好的选择。

据我所知,如今最流行的“好”的伪随机数发生器(PRNG),通常用于游戏应用中,是Mersenne Twister。这里有一个.NET实现。该算法通过了所有Diehard Tests的随机分布测试;它几乎没有偏差,并且在您使用随机数进行概率和统计应用时是一个很好的选择。
GNU Scientific Library也有许多RNG算法,并且不出所料,Mersenne Twister排名第一。但是其他一些算法出于好奇也值得一看;RANLUX在我记忆中的diehard测试中得分也相当高。

当然,Eric的评论是正确的;如果您不具有关于需要多么随机的特定技术要求,则所有这些信息都是无用的。我正在使用一个适用于相对低影响的赌博/游戏应用程序的定义(即不是每天有数百万访问者的主要注册赌博网站-这些网站对随机性有更严格的规定)。


C#库不是基于Knuth的方法,他们在编码时出了差错。 - evolvedmicrobe
@evolvedmicrobe:这是他们文档中的说法。你有引用或其他证据来证明相反吗? - Aaronaught
是的,他们在这里尝试过,并且我通过反编译进行了验证:http://connect.microsoft.com/VisualStudio/feedback/details/634761/system-random-serious-bug - evolvedmicrobe
@evolvedmicrobe:好的,虽然我几乎认为这样一个微小的差异对这个答案的实质没有任何影响(或者克里斯的答案也是如此——无论周期是什么,PRNG是否符合要求都没有区别)。 - Aaronaught
@aeronaught 并不是说这会影响答案的实质,而且原始问题缺乏具体的要求,谁知道那个问题的答案是什么。不过阅读文章(或 MSDN 文档)的人可能会认为快速算法是基于 Knuth 的,而实际上并不是,所以我想帮助其他人避免像我一样被绊倒。 - evolvedmicrobe

4

要生成随机数,需创建一个Random类的对象,然后使用该对象的Next函数来生成随机数。它有许多重载,例如:

Next(int minimum, int maximum);
Next(int Maximum);

您可以指定要生成的随机数的最小和最大范围。

代码片段:

Random random = new Random();
int maxValue = 100;

int r = random.Next(maxValue);

3
System.Random类存在很多问题,与要求不符。理论上,它应该比其他许多伪随机生成器提供更好的结果。它是一个直接和文字描述的Lagged Fibonacci Generator(LFG)示例C代码移植自"Numerical Recipes in C"第二版第283页(代码在后续版本中被重新编写)。 LFG使用比许多其他库(例如Java)使用的Linear Congruential Generators(LCGs)更好的算法。
不幸的是,Microsoft对System.Random类的实现存在漏洞。有关更多信息,请参见http://connect.microsoft.com/VisualStudio/feedback/details/634761/system-random-serious-bug。看起来有人不小心输入了“21”而不是“31”。这妨碍了算法的伪随机特性。链接包括MS的解释,说明他们为什么在此阶段无法修复错误。

感谢您详细的回复。我们最终使用了硬件随机数生成器,但还是非常感谢您提供的信息和链接! - LiamB
在负数种子值的映射中存在另一个 bug。这些被映射到它们的绝对值,但如果种子是int.MinValue,则会被映射到int.MaxValue。这意味着有三个输入种子映射到int.MaxValue,只有一个映射到零,而每个正整数(除了int.MaxValue)对应两个输入值。这意味着有一条序列的概率高50%被选择,而另一条序列的概率低50%。不幸的是,连接链接已经失效。 - phoog

1

如果您正在寻找真正的随机数,那么您应该考虑使用利用自然现象的在线随机数生成器,例如http://www.random.org,它使用大气噪声。真正的随机数也是伪随机数生成器的良好种子。

Sipwiz在他的答案中展示了如何在C#中使用它:Generate random values in C#。这里也讨论了这个问题:http://www.vcskicks.com/random-number-generator.php

随机数生成器有很多方面。一个有趣的替代实现是ISSAC(ttp://burtleburtle.net/bob/rand/isaac.html),它还包含了对偏差等内容的良好讨论,也有C#版本(http://burtleburtle.net/bob/rand/isaacafa.html)。


-1

使用.NET的Random应该可以胜任这个任务:

var random = new Random(System.DateTime.Now.Millisecond);

int randomNumber = random.Next(1, 5000000);

1
这仅适用于局部变量,那是一场即将发生的意外。 - Hans Passant
应该是 int randomNumber = random.Next(1, 5000001); 因为 .Net 方法返回的数字包括下限但不包括上限。 - Ed Power
如果毫秒是足够好的种子,那么取决于数字在哪个上下文中使用。老虎机就是这样被黑客攻击的。 - Paco
4
这个只提供了1000个不同的种子,很少改变...这甚至比使用“new Random()”还糟糕。 - CodesInChaos

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