【问题标题】:What is the quality of Random class implementation in .NET?.NET 中 Random 类实现的质量如何?
【发布时间】:2026-01-06 03:35:02
【问题描述】:

我有两个关于在 .NET Framework 4.6 中实现 Random 类的问题(代码可用 here):

  1. 在构造函数末尾将Seed 参数设置为1 的基本原理是什么?它似乎是从 Numerical Recipes in C (2nd Ed.) 中复制粘贴的,它在其中有意义,但在 C# 中没有。

  2. 在书(Numerical Recipes in C (2nd Ed.))中直接说明inextp 字段设置为值31,因为:

常数 31 是特殊的;见高德纳。

但是,在 .NET 实现中,此字段设置为值 21。为什么?除了这个细节之外,其余代码似乎与书中的代码密切相关。

【问题讨论】:

  • 如果你需要他,@EricLippert 在哪里?
  • 但是,在 .NET 实现中,此字段设置为值 21。为什么? 他们只是选择了一个随机数 ;)
  • 此外,还有一个常量 MZ 根本没有使用,取而代之的是值0 只是在那些地方使用。
  • 请注意,31 显然是特殊的,因为它是两个系数之间的差异 - 55 和 24。使用 21 似乎意味着 .NET 实现的周期比原始的更短31. 至于设置Seed,这在C#中确实没有效果。

标签: c# .net random


【解决方案1】:

关于intexp 问题,这是一个错误,Microsoft has acknowledged and refused to fix 出于向后兼容性问题。

确实,您发现了 Random 实现的真正问题。 我们已经在团队内部以及与我们的一些合作伙伴进行了讨论,并得出结论,很遗憾,我们现在无法解决问题。原因是一些应用程序依赖于这样一个事实,即当使用相同的种子初始化时,生成器会产生相同的伪随机序列。即使更改变得更好,一旦迁移到“固定”版本,它也会破坏做出此假设的应用程序。

【讨论】:

  • 如果您将intexp 设置为31 而不是21,我不明白为什么生成器不再生成具有相同种子的相同伪随机序列。
  • 算法保留一个由 56 个随机整数组成的缓冲区,这些整数是根据种子值初始化的。缓冲区中的两个索引应保持 31 个位置(在本例中为 21 个),伪随机数计算为这些索引处的值的差。您不能在不更改输出的情况下更改索引的位置,因为它们会引用不同的值。
  • @TimSchmelter:使用新的31 实现,它将产生一个不同于21 实现的序列。为了极其简化,Random.next() 是种子和 31 或 21 值的函数。改变值,改变next的行为。
  • @njzk2:所以它仍然会产生具有相同种子的相同序列,但与旧实现相比它会产生不同的序列?好的,那么它将破坏依赖它的现有系统。谢谢。
  • @TimSchmelter 完全正确。
【解决方案2】:

更多上下文:

不久前,我全面分析了这个实现。我发现了一些不同之处。

第一个(非常好)是一个不同的大值(MBIG)。 Numerical Recipies 声称 Knuth 明确表示任何大的值都应该起作用,所以这不是问题,微软合理地选择使用 32 位整数的最大值。

第二个就是那个常数,你提到了。那是一件大事。在最低限度,它将大大减少周期。有报道说效果实际上比这更糟。

但随之而来的是另一个特别令人讨厌的区别。从字面上保证,它会偏置输出(因为它直接这样做),并且也可能会影响 RNG 的周期。

那么第二个问题是什么?当 .NET 第一次出现时,微软并没有意识到他们编码的 RNG 在两端都是包容性的,他们将其记录为在最大端是独占的。为了解决这个问题,安全团队添加了一行相当邪恶的代码:if (retVal == MBIG) retVal--;。这是非常不幸的,因为正确的修复实际上只是添加了 4 个字符(加上空格)。

正确的解决方法是将MBIG 更改为int.MaxValue-1,但将Sample() 切换为使用MBIG+1(即继续使用int.MaxValue)。这将保证 Sample 的范围为 [0.0, 1.0) 而不会引入任何偏差,并且只会改变 MBIG 的值,Numerical Recipies 说 Knuth 说这很好。

【讨论】:

    最近更新 更多