以下代码似乎没有直观的行为:
#include <random>
#include <iostream>
using namespace std;
int main()
{
mt19937 MyGenerator(40);
auto gauss = normal_distribution<double>(0,1);
auto linear = uniform_real_distribution<double>(0,1);
cout << gauss(MyGenerator) << endl; //line a
cout << linear(MyGenerator) << endl; //line b
cout << gauss(MyGenerator) << endl;
}
运行此代码会提供输出
-0.816097
0.705030
0.303032.
如果现在交换行a和b的顺序,则输出变为
0.644008
0.338080
-0.639501.
完全清楚的是,前两个数字现在是不同的,因为它们是由不同的分布产生的。不过,为什么第三个数字不同? 在我的直觉中,分布应该获取一个数字c = MyGenerator(),然后将其映射到特定范围内的随机数。随机数生成器将指向分发调用之后的数字序列中的下一个数字。那么,在这两种情况下,第三次调用的结果是否应该相同?
另一个观察: 事实上,向任一发行版添加第四次调用似乎都会重现相同的数字。
答案 0 :(得分:13)
libstdc ++ normal_distribution
的实现使用Marsaglia polar method。这个方法的有趣之处在于每个传递使用来自URNG的两个随机数来生成两个结果。
也就是说,对分配的第一次调用会调用URNG两次(可能更多次,因为它使用拒绝采样,但偶数次)并返回一个结果;以下对分配的调用不会调用URNG,但会返回保存的第二个结果。
这是一个extract from the source code,略有重新格式化:
if (_M_saved_available)
{
_M_saved_available = false;
ret = _M_saved;
}
else
{
result_type x, y, r2;
do
{
x = result_type(2.0) * aurng() - 1.0;
y = result_type(2.0) * aurng() - 1.0;
r2 = x * x + y * y;
}
while (r2 > 1.0 || r2 == 0.0);
const result_type mult = std::sqrt(-2 * std::log(r2) / r2);
_M_saved = x * mult;
_M_saved_available = true;
ret = y * mult;
}
答案 1 :(得分:9)
没有要求分发为每个值调用基础生成器一次。一些分布最好通过组合多个随机值来计算。
例如,在GNU实现中,统一分布的实现是
return (__aurng() * (__p.b() - __p.a())) + __p.a();
调用生成器__aurng
一次;而正态分布的核心是:
do
{
__x = result_type(2.0) * __aurng() - 1.0;
__y = result_type(2.0) * __aurng() - 1.0;
__r2 = __x * __x + __y * __y;
}
while (__r2 > 1.0 || __r2 == 0.0);
至少调用两次。