使用“<random>”的不可重现的随机数

时间:2017-08-29 10:31:21

标签: c++ random

我正在尝试创建一个为多个发行版生成随机数的类,同时保持它们的可重现性(通过设置初始种子)。

代码似乎有效,直到我开始使用正态分布和奇怪的错误表面。这些主要是:

  • 如果我取消注释double a = rnd.rnorm(0.0, 1.0); - 行(第40行)(即如果我在设置种子之前调用rnorm),则正态分布的第一个随机数不再匹配,随机之后的数字再次匹配
  • 如果我从正态分布中检索奇数个随机数,则将正常随机数移一(例如将第39行设置为int n = 3;
  • 如果我一起做这两件事,随机数会在另一个方向上移动一个(领先)

现在我的问题是,是什么导致了这种奇怪的行为?我是否以错误的方式实施了RNG?最重要的是,我该如何解决?

代码

如果您想亲自测试结果,可以使用此http://cpp.sh/9phre

或者

#include <stdio.h>
#include <random>

// Class to create random numbers 
// Main functions to set the seed: setseed()
// create uniformly distributed values: runif()
// and normally distributed values: rnorm()
class RNG {
public:
    RNG(int seed = (int) time(0)) {
        setseed(seed);
    };
    ~RNG() {};
    void setseed(int newSeed) {
        re.seed(newSeed);
    };

    double runif(double minNum, double maxNum) {
        return dud(re, distUnifDbl::param_type{minNum, maxNum});
    };
    double rnorm(double mu, double sd) {
        return dnd(re, distNormDbl::param_type{mu, sd});
    };

private:
    // take the Mersenne-Twister Engine
    std::mt19937 re {};
    // create the uniform distribution
    using distUnifDbl = std::uniform_real_distribution<double>;
    distUnifDbl dud {};
    // create the normal distribution
    using distNormDbl = std::normal_distribution<double>;
    distNormDbl dnd {};

};

int main(int argc, char const *argv[]) {
    RNG rnd;
    int n = 4; // setting n to an odd number, makes _all_ normal numbers non-reproducible
    //double a = rnd.rnorm(0.0, 1.0); // uncommenting this, makes the _first_ normal number non-reproducible

    printf("Testing some Uniform Numbers\n");
    rnd.setseed(123);
    for (int i = 0; i < n; ++i) {
        printf("% 13.10f ", rnd.runif(0.0, 1.0));
    }
    rnd.setseed(123);
    printf("\n");
    for (int i = 0; i < n; ++i) {
        printf("% 13.10f ", rnd.runif(0.0, 1.0));
    }
    printf("\n");

    printf("\nTesting some Normal Numbers\n");
    rnd.setseed(123);
    for (int i = 0; i < n; ++i) {
        printf("% 13.10f ", rnd.rnorm(0.0, 1.0));
    }
    rnd.setseed(123);
    printf("\n");
    for (int i = 0; i < n; ++i) {
        printf("% 13.10f ", rnd.rnorm(0.0, 1.0));
    }
    printf("\n");
    return 0;
}

结果

基本情况

设置n = 4并留下a时,我会收到以下内容(这正是我想要/需要的;可重复的&#34;随机&#34;数字):

Testing some Uniform Numbers
 0.7129553216  0.4284709250  0.6908848514  0.7191503089 
 0.7129553216  0.4284709250  0.6908848514  0.7191503089 

Testing some Normal Numbers
-0.5696096995  1.6958337120  1.1108714913  0.9675940713 
-0.5696096995  1.6958337120  1.1108714913  0.9675940713 

错误1

现在出错了。设置n = 5(或任何奇数),我会收到:

Testing some Uniform Numbers
 0.7129553216  0.4284709250  0.6908848514  0.7191503089  0.4911189328 
 0.7129553216  0.4284709250  0.6908848514  0.7191503089  0.4911189328 

Testing some Normal Numbers
-0.5696096995  1.6958337120  1.1108714913  0.9675940713  1.5213608069 
-0.0482498863 -0.5696096995  1.6958337120  1.1108714913  0.9675940713 

显然将所有正常数字强调为1.均匀数字保持不变(我猜这很好)。

错误2

取消注释一行(即,在设置种子之前}之前调用rnd.rnorm(0.0, 1.0))会导致以下输出(带n = 4或任何其他偶数)

Testing some Uniform Numbers
 0.7129553216  0.4284709250  0.6908848514  0.7191503089 
 0.7129553216  0.4284709250  0.6908848514  0.7191503089 

Testing some Normal Numbers
 0.9761557076 -0.5696096995  1.6958337120  1.1108714913 
 0.9675940713 -0.5696096995  1.6958337120  1.1108714913 

这显然打破只有第一个正常随机数,再次保留统一数字。

错误3

将两个点一起使用(保留行未注释并将n设置为奇数),我得到了这个

Testing some Uniform Numbers
 0.7129553216  0.4284709250  0.6908848514  0.7191503089  0.4911189328 
 0.7129553216  0.4284709250  0.6908848514  0.7191503089  0.4911189328 

Testing some Normal Numbers
-0.4553400276 -0.5696096995  1.6958337120  1.1108714913  0.9675940713 
-0.5696096995  1.6958337120  1.1108714913  0.9675940713  1.5213608069 

现在,第二个正常随机数被移到另一个方向(引导)。

系统规范

我在Ubuntu 16.04和g++ --version g++(Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609

上使用此功能

更新

它似乎没有连接到特定的生成器,即用std::mt19937 re {};std:: linear_congruential_engine<std::uint_fast32_t, 48271, 0, 2147483647> re {};替换std::subtract_with_carry_engine<std::uint_fast64_t, 48, 5, 12> re{};导致相同的行为(但显然与不同的数字)。

2 个答案:

答案 0 :(得分:6)

void setseed(int newSeed) {
        re.seed(newSeed);
        dud.reset(); // <---- 
        dnd.reset(); 
    };

发行版具有内部状态。您需要重置它才能再次获得相同的序列。

答案 1 :(得分:2)

如果您关心可重现的“随机”数,则应避免使用C ++分布,包括uniform_real_distributionnormal_distribution,而应依靠自己的方式从{{1} }转换成您想要的数字。 (例如,我为uniform floating-point numbers提供了方法。请注意,当重现性很重要时,会有other things to consider。)

C ++分发类,例如mt19937have no standard implementation。结果,即使将相同的种子传递给这些发行版,它们传递的数字顺序也可能会有所不同,甚至每次运行都不同,这取决于实现这些发行版的方式。请注意,不是使用“编译器”,“操作系统”还是“体系结构”来决定使用哪种算法,而是由C ++标准库实现来决定。另请参见this question

另一方面,诸如uniform_real_distribution之类的随机引擎确实具有保证的实现;在所有兼容的C ++库实现(包括不同“体系结构”的实现)中,它们将为相同的种子返回相同的随机数,甚至在每次运行时都返回相同的随机数。

另请参阅以下问题:Generate the same sequence of random numbers in C++ from a given seed