对观察到的行为的解释
如果可能结果的范围小于 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;
其中__urange 是larger - smaller 和__urngrange 是rng 可以返回的最大值和最小值之间的差。 (来自 libstdc++ 6.1 中 bits/uniform_int_dist.h 的代码)
在我们的例子中,rng default_random_engine 是一个 minstd_rand0,它为您测试的范围 [0,10] 生成 __scaling == 195225785。因此,如果rng() < 195225785,则分布将返回 0。
minstd_rand0 返回的第一个数字是
(16807 * seed) % 2147483647
(其中seed == 0 调整为1 顺便说一句)。因此,我们可以看到,由小于 11615 的数字播种的 minstd_rand0 产生的第一个值将使用您使用的 uniform_int_distribution< int > 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 的统计属性也远远优于。
您有时听到的关于状态空间大或生成速度慢的问题通常在嵌入式世界之外无关紧要。根据boost 和cacert.at 的说法,MT 甚至比minstd_rand0 还要快。
尽管如此,您仍然需要使用丢弃技巧,即使您的结果在肉眼看来看起来不错。在我的系统上它只需要不到一毫秒,而且你不会经常播种 rng,所以没有理由不这样做。
请注意,我无法准确估计我们需要的丢弃数量,我从this answer 中获取了这个值,它链接this paper 是为了合理。我现在没有时间解决这个问题。