【问题标题】:Random number generation, two different methods?随机数生成,两种不同的方法?
【发布时间】:2020-08-07 17:53:40
【问题描述】:

有谁知道以下代码中使用的两种随机数生成方法之间的区别?我怀疑第一种情况的计算成本可能更高,因为每次生成随机数时它都会从/dev/urandom 获得一个新种子。如果我在这些示例中正确生成随机数,也感谢任何 cmets。

我很困惑,因为uniform_int_distribution 指定了template<class URNG> result_type operator()(URNG& g);,在某些示例中,我看到传递的参数是default_random_engine 类型,而其他时候是random_device。这让 default_random_engine 的实际作用更加混乱。

比如我见过的方法一:

#include <iostream>
#include <random>
#include <map>
using namespace std;

int main()
{
    random_device rd;
    uniform_int_distribution<int> p{0,9};

    map<int,int> m;
    for (int i=0; i < 100; ++i) {
        m[p(rd)]++;
    }

    for (map<int,int>::iterator it = m.begin();
         it != m.end(); ++it)
        cout << it->first << ", " << it->second << '\n';
    
    return 0;
}

方法二,

#include <iostream>
#include <random>
#include <map>
using namespace std;

int main()
{
    random_device rd;
    default_random_engine gen(rd());
    uniform_int_distribution<int> p{0,9};

    map<int,int> m;
    for (int i=0; i < 100; ++i) {
        m[p(gen)]++;
    }

    for (map<int,int>::iterator it = m.begin();
         it != m.end(); ++it)
        cout << it->first << ", " << it->second << '\n';
    
    return 0;
}

【问题讨论】:

  • 第一种方法简直太糟糕了。你的硬件熵有限,你想这样浪费它吗?第二种方法可以使用优于 default_random_engine 的 PRNG 进行改进,我认为这是实现定义的。
  • 不要永远使用default_random_engine。你不知道你会得到什么。您可能会得到一个线性同余生成器(或更糟)。明确地命名您想要的随机引擎 - 并正确seed它。如果您不知道为什么应该选择其他东西,mt19937_64 是一个很好的默认选择。
  • default_random_engine 这样的 PRNG 实际上在做什么?我在cplusplus.com/reference/random/linear_congruential_engine/… 上看到它使用x = (a*x + c) mod m,但不知道x, a,c, m 是基于该页面描述的参数。

标签: c++ c++11


【解决方案1】:

第一种方法是不好的做法,第二种方法是首选。虽然,在第二种方法中,default_random_engine 通常不被推荐,除非它是为了实验或其他一些不重要的原因。很大的原因是它是实现定义的,所以它会根据编译器而有所不同,而且它通常根本不健壮,只是简单。

第一种方法不好的原因是std::random_device 轮询硬件的真正随机性来源。这是一个有限的供应。更好的做法是将其用作伪随机数生成器 (PRNG) 的完全不可预测的种子。这就是为什么第二个例子更好。

我对您的第二个示例进行了一些更改。主要变化是我不需要声明 random_device 就可以坐在堆栈上,我使用 std::mt19937 这可能是您在 C++ 代码中看到的最常见的 PRNG。

没有一个 C++ 标准库 PRNG 被认为足以用于加密目的;你需要一个外部库。

#include <iostream>
#include <map>
#include <random>

int main() {
  std::mt19937 gen(std::random_device{}());
  std::uniform_int_distribution<int> p{0, 9};

  std::map<int, int> m;
  for (int i = 0; i < 100; ++i) {
    m[p(gen)]++;
  }

  for (auto it = m.begin(); it != m.end(); ++it) {
    std::cout << it->first << ", " << it->second << '\n';
  }

  return 0;
}

【讨论】:

  • “轮询硬件的真正随机性来源” 或者它应该这样做(*cough* MinGW *cough*)。
  • 对,不过觉得这个答案不需要提高。
  • uniform_int_distribution{}() 是否修改传入的随机引擎?我注意到重载()不是参数的常量。试图了解这些对象中是否/如何维护状态。
  • 我不能说这个,因为我从来不需要知道。封装等等。不过,PRNG 在生成值后必须更改其状态似乎很自然。与分布相同。
【解决方案2】:

std 库提供了一组Predefined random number generators,它为生成的值提供了某些保证。其中之一是default_random_engine,它是实现定义的(因此您需要检查库的供应商是如何实现它的)。

要拥有可靠的生成器(不同供应商之间的结果一致),您可以选择其他生成器之一,例如 mt19937

您从random_device 获得的随机数通常仅用作所选随机数生成器的种子。对于random_device,您会遇到与default_random_engine 类似的问题,因为没有指定如何生成值。

这背后的想法是,random_device 通常为种子提供足够好的值,但对于适当的生成器来说不够好。

如果您知道您的程序运行的特定硬件和库,并且您知道 random_device 根据良好的来源创建随机数,那么它可能是一个选择,但如果您创建一个使用的应用程序在不同的操作系统和未知硬件上,random_device 将不能作为生成器的选项。

【讨论】:

  • 所以random_device 充当一些种子,生成器生成的数字是伪随机的?让我想知道种子需要多久更新一次。
  • @notaorb 您在开始时只设置了一次种子。并且根据每次运行的随机性是否应可重现,您可以为种子使用常量值或随机值。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-13
  • 2017-09-11
  • 2014-10-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多