【问题标题】:C++ uniform_int_distribution always returning min() on first invocationC++ uniform_int_distribution 总是在第一次调用时返回 min()
【发布时间】:2014-03-17 13:56:25
【问题描述】:

在标准库的至少一个实现中,std::uniform_int_distribution<> 的第一次调用确实返回随机值,而是返回分布的最小值。也就是说,给定代码:

default_random_engine engine( any_seed() );
uniform_int_distribution< int > distribution( smaller, larger );
auto x = distribution( engine );
assert( x == smaller );

...x 对于any_seed()smallerlarger 的任何值实际上都是smaller

要在家玩,你可以试试code sample,它在 gcc 4.8.1 中演示了这个问题。

我相信这是正确的行为?如果是正确的行为,为什么随机分布会返回这个明显非随机的值?

【问题讨论】:

  • 真的吗? ideone.com/Xm9tRu
  • 是的,真的。您是否尝试过链接的代码示例?使用 time() 确实揭示了一些东西,那就是对于非常大的种子,问题就消失了。但是有很多好的案例需要一个小的、固定的种子,而在这些案例中问题显然存在。
  • 这也是gcc唯一的问题,在VS13中,小到2的种子会产生smaller以外的数字
  • 我很确定我也在 VS2012 中看到了它。 :(

标签: c++ gcc random


【解决方案1】:

对观察到的行为的解释

如果可能结果的范围小于 rng 产生的数字范围,这就是 uniform_int_distribution 将随机位映射到数字的方式:

const __uctype __uerange = __urange + 1; // __urange can be zero
const __uctype __scaling = __urngrange / __uerange;
const __uctype __past = __uerange * __scaling;
do
  __ret = __uctype(__urng()) - __urngmin;
while (__ret >= __past);
__ret /= __scaling;

其中__urangelarger - smaller__urngrange 是rng 可以返回的最大值和最小值之间的差。 (来自 libstdc++ 6.1 中 bits/uniform_int_dist.h 的代码)

在我们的例子中,rng default_random_engine 是一个 minstd_rand0,它为您测试的范围 [0,10] 生成 __scaling == 195225785。因此,如果rng() &lt; 195225785,则分布将返回 0。

minstd_rand0 返回的第一个数字是

(16807 * seed) % 2147483647

(其中seed == 0 调整为1 顺便说一句)。因此,我们可以看到,由小于 11615 的数字播种的 minstd_rand0 产生的第一个值将使用您使用的 uniform_int_distribution&lt; int &gt; distribution( 0, 10 ); 产生 0。 (就我而言,mod off-by-one-errors。;))

你提到了更大种子的问题:一旦种子变得足够大以实际使 mod 操作做某事,我们不能简单地通过除法将整个范围的值分配给相同的输出,所以结果将看起来更好。

这是否意味着(libstdc++ 的 impl of) 已损坏?

没有。您通过始终选择较小的随机 32 位种子引入了显着偏差。结果中出现的这种偏见并不令人惊讶或邪恶。对于随机种子,即使您的 minstd_rand0 也会产生一个相当均匀随机的第一个值。 (虽然之后的数字序列不会有很好的统计质量。)

对此我们能做些什么?

案例1:你想要统计质量高的随机数。

为此,您可以使用更好的 rng(如 mt19937)并为其整个状态空间播种。对于 Mersenne Twister,这是 624 个 32 位整数。 (作为参考,here 是我尝试正确执行此操作,并在答案中提供了一些有用的建议。)

案例 2:您真的只想使用那些小种子。

我们仍然可以从中获得不错的结果。问题是伪随机数生成器通常“有点连续”地依赖于它们的种子。为了解决这个问题,我们丢弃了足够多的数字,让最初相似的输出序列发散。因此,如果您的种子必须很小,您可以像这样初始化您的 rng:

std::mt19937 rng(smallSeed);
rng.discard(700000);

为此,您必须使用像 Mersenne Twister 这样的好的 rng。我不知道有什么方法可以从种子不佳的minstd_rand0 中获得不错的价值,例如参见this train-wreck。即使正确播种,mt19937 的统计属性也远远优于。

您有时听到的关于状态空间大或生成速度慢的问题通常在嵌入式世界之外无关紧要。根据boostcacert.at 的说法,MT 甚至比minstd_rand0 还要快。

尽管如此,您仍然需要使用丢弃技巧,即使您的结果在肉眼看来看起来不错。在我的系统上它只需要不到一毫秒,而且你不会经常播种 rng,所以没有理由不这样做。

请注意,我无法准确估计我们需要的丢弃数量,我从this answer 中获取了这个值,它链接this paper 是为了合理。我现在没有时间解决这个问题。

【讨论】:

  • 有点切线……我偶然发现了__scaling == 195225785 部分。这是正确的,但不是显而易见的 IMO(为什么不是 195225786?)。因为minstd_rand0返回的最大值是2^31-2(x模2^31-1),最小值是1,所以能返回的不同值的个数也是2^31-2,这恰好比 11 的下一个倍数小 1,因此除法将截断为 195225785。(不能返回 0,这是因为用 0 播种被转换为用 1 播种,以及一些群论,但我赢了'现在不要进入那个......)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-24
  • 2012-05-02
相关资源
最近更新 更多