第一个起作用,但是第二个总是返回相同的值。为什么会发生这种情况,我该如何解决?
int main() {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> dis(0, 1);
for(int i = 0; i < 10; i++) {
std::cout << dis(gen) << std::endl;
}return 0;
}
一个不起作用:
double generateRandomNumber() {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> dis(0, 1);
return dis(gen);
}
int main() {
for(int i = 0; i < 10; i++) {
std::cout << generateRandomNumber() << std::endl;
}return 0;
}
答案 0 :(得分:5)
您正在使用什么平台?如果不存在用于生成随机数的硬件或操作系统功能,则std::random_device
可以是伪RNG。它可能使用当前时间进行初始化,在这种情况下,您调用它的时间间隔可能相隔太近,以至于“当前时间”无法采用其他值。
尽管如此,如评论中所述,它并不意味着以这种方式使用。一个简单的解决方法是将rd
和gen
声明为static
。一个适当的解决方法是将RNG的初始化移出需要随机数的函数,以便它也可以被需要随机数的其他函数使用。
答案 1 :(得分:3)
第一个为所有数字使用相同的生成器,第二个为每个数字创建一个新的生成器。
答案 2 :(得分:2)
请注意,std::mt19937 gen(rd())
很有问题。参见this question,其中说:
此外,random_device
的生成“不确定”随机数的方法是“实现定义的”,并且random_device
允许实现“使用随机数引擎”(如果它不能生成)由于“实现限制”([rand.device])而导致的“不确定”随机数。 (例如,在C ++标准下,实现可能使用来自系统时钟的时间戳或使用快速移动的循环计数器来实现random_device
,因为两者都是不确定的。)
应用程序不应盲目调用random_device
的生成器(rd()
),而至少也不要调用entropy()
方法,该方法以位为单位估计实现的熵
答案 3 :(得分:1)
让我们比较一下您的两种情况之间的区别,并了解为什么会发生这种情况。
案例1:
int main() { std::random_device rd; std::mt19937 gen(rd()); std::uniform_real_distribution<> dis(0, 1); for(int i = 0; i < 10; i++) { std::cout << dis(gen) << std::endl; }return 0; }
在第一种情况下,程序将执行主要功能,并且首先发生的事情是在堆栈上创建std::random_device
,std::mt19337
和std::uniform_real_distribution<>
的实例属于main()
的范围。您的mersenne捻线机gen
会根据您的随机设备rd
的结果进行一次初始化。您还初始化了分布dis
,使其值的范围从0
到1
。每次运行该应用程序时,它们仅存在一次。
现在,您创建一个从索引0
开始并递增到9
的for循环,并在每次迭代中使用分布{{1}将结果值显示到cout
。 }的dis
传递给您已经播种的一代operator()()
。每次在此循环上gen
都会产生一个不同的值,因为dis(gen)
仅被播种了一次。
情况2:
gen
在此版本的代码中,让我们看看相似之处和不同之处。程序在这里执行并进入double generateRandomNumber() {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> dis(0, 1);
return dis(gen);
}
int main() {
for(int i = 0; i < 10; i++) {
std::cout << generateRandomNumber() << std::endl;
}return 0;
}
函数。这次遇到的第一件事是一个从main()
到0
的for循环,类似于上面的main中的循环,但是此循环是main堆栈中的第一件事。然后调用9
以显示名为cout
的{{1}}的结果。该函数总共被调用user defined function
次,每次您遍历for循环时,此函数都有其自己的堆栈存储器,该堆栈存储器将被缠绕,展开或创建和销毁。
现在让我们跳到名为generateRandomNumber()
的{{1}}的执行。
代码看起来与之前直接放在10
中时几乎完全一样,但是这些变量位于user defined function
的堆栈中,并具有其作用域的生存时间。每当此函数进入和超出范围时,将创建和销毁这些变量。此处的另一个区别是此函数还返回generateRandomNumber()
。
注意:我不确定
main()
是否会返回generateRandomNumber()
还是编译器最终会进行某种优化,但是按值返回通常会导致副本。
最后,当函数dis(gen)
返回并且恰好在其完全超出作用域之前,在其中调用100%
的{{1}},并且在返回之前进入它自己的堆栈和作用域如此短暂地到达主copy
,然后再回到主。
-可视化差异-
如您所见,这两个程序是完全不同的,确切地说是非常不同的。如果您想获得更多视觉差异的证明,可以使用任何可用的在线编译器将每个程序输入到generateRandomNumber()
中显示该程序的位置,并比较两个程序集版本以查看其最终差异。
可视化这两个程序之间差异的另一种方法不仅是查看它们的std::uniform_real_distribrution<>
等效项,而且还需要逐行使用operator()()
逐步浏览每个程序,并密切注意{{1 }}以及它们的缠绕和展开,并在它们被初始化,返回和销毁时留意所有值。
-评估与推理-
第一个按预期运行的原因是因为您的generateRandomNumber()
,assembly
和assembly
的生命周期均为debugger
和{{1} }仅在您的随机设备中播种一次,并且每次在for循环中只有一次使用的发行版。
在您的第二个版本中,main对此一无所知,它所知道的只是它正在经历for循环并将返回的数据从用户函数发送到cout。现在,每当它通过for循环时,都会调用此函数,并且每次创建和销毁它时都按我说的那样堆栈,因此所有其变量都将被创建和销毁。因此,在这种情况下,您正在创建和销毁stack calls
random device
,generator
和distribution
的实例。
-结论-
除了我上面描述的内容之外,还有更多内容,而与您的随机数生成器行为有关的另一部分是用户main
在他对问题的评论中对您的陈述中提到的内容:
来自en.cppreference.com/w/cpp/numeric/random/random_device: “ std :: random_device可以根据 实现定义的伪随机数引擎。 在这种情况下,每个std :: random_device对象都可以生成 相同的数字顺序。”
每次创建和销毁时,都在generator
上反复填充新的10:
,但是如果您的特定计算机或操作系统不支持使用rd
,可以最终使用某个任意值作为其种子值,也可能最终使用系统时钟生成一个种子值。
因此,我们可以说它确实使用了系统时钟,gen(rd())
的for循环的执行是如此之快,以至于dis(0,1)
调用{{ 1}}已经执行了几毫秒。因此,此处的增量时间最小且可忽略不计,因为它在每次通过时都生成相同的种子值,并且从分布中生成相同的值。