C++uniform_int_distribution在第一次调用时总是返回min()

C++ uniform_int_distribution always returning min() on first invocation

本文关键字:返回 min 调用 distribution int 第一次 C++uniform      更新时间:2023-10-16

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

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

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

要在家里玩,您可以尝试在gcc 4.8.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)

在我们的例子中,rngdefault_random_engineminstd_rand0,它在您测试的[0,10]范围内产生__scaling == 195225785。因此,如果rng() < 195225785,则分布将返回0。

minstd_rand0返回的第一个数字是

(16807 * seed) % 2147483647

(其中seed == 0被调整为1btw)。因此,我们可以看到,使用您使用的uniform_int_distribution< int > distribution( 0, 10 );,由编号小于11615的minstd_rand0种子产生的第一个值将产生0。(由于我的一个错误而被修改。;))

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

这是否意味着(libstdc++的impl of)<随机>坏了吗

没有。你总是选择一个小的随机32位种子,从而在这个种子中引入了显著的偏差。结果中出现的这种偏见并不奇怪,也不邪恶。对于随机种子,即使是minstd_rand0也会产生一个相当均匀的随机第一个值。(尽管之后的数字序列不会有很好的统计质量。)

我们该怎么办

情况1:您想要高统计质量的随机数。

为此,您可以使用一个更好的rng,如mt19937,并为其整个状态空间播种。对于Mersenne Twister,这是624个32位整数。(作为参考,以下是我在回答中提出的一些有用的建议,试图正确地做到这一点。)

情况2:您真的只想使用那些小种子。

我们仍然可以从中获得不错的结果。问题是伪随机数生成器通常依赖于"伪随机数";有点连续";在他们的种子上。为了绕过这一点,我们丢弃了足够多的数字,让最初相似的输出序列发散。因此,如果你的种子必须很小,你可以这样初始化你的rng:

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

这是至关重要的,你使用一个好的rng像梅森扭曲。我不知道有什么方法可以从一个种子不好的minstd_rand0中获得甚至不错的值,例如,看到这个火车残骸。即使适当地播种,mt19937的统计特性到目前为止也是优越的。

你有时听到的对大状态空间或慢生成的担忧,在嵌入式世界之外通常是无关紧要的。根据boost和cacert.at,MT甚至比minstd_rand0快得多。

尽管如此,你仍然需要做丢弃的把戏,即使你的结果在没有丢弃的情况下肉眼看起来很好。这在我的系统上只需要不到一毫秒的时间,而且你不经常播种rng,所以没有理由不这样做。

请注意,我无法给你一个我们需要的丢弃数量的精确估计,我从这个答案中得出了这个值,它将本文联系起来是合理的。我现在没有时间解决这个问题。