<random>在Linux中生成相同的数字,但在Windows中生成相同的数字


90

下面的代码用于在间隔[1,100]中生成五个伪随机数的列表。我为default_random_enginewith设置了种子time(0),它以unix时间返回系统时间。当我使用Microsoft Visual Studio 2013在Windows 7上编译并运行该程序时,它会按预期运行(请参阅下文)。但是,当我在Arch Linux中使用g ++编译器执行此操作时,它的行为就很奇怪。

在Linux中,每次将生成5个数字。每次执行后4个数字将有所不同(通常是这样),但第一个数字将保持不变。

Windows和Linux上5次执行的示例输出:

      | Windows:       | Linux:        
---------------------------------------
Run 1 | 54,01,91,73,68 | 25,38,40,42,21
Run 2 | 46,24,16,93,82 | 25,78,66,80,81
Run 3 | 86,36,33,63,05 | 25,17,93,17,40
Run 4 | 75,79,66,23,84 | 25,70,95,01,54
Run 5 | 64,36,32,44,85 | 25,09,22,38,13

更令人迷惑的是,在Linux上,第一个数字会定期增加一个。获得上述输出后,我等待了大约30分钟,然后再次尝试发现第一个数字已更改,现在始终生成为26。它一直定期递增1,现在为32。它似乎对应随着价值的变化time(0)。

为什么第一个数字在每次运行中很少改变,然后又增加1?

代码。它整齐地打印出5个数字和系统时间:

#include <iostream>
#include <random>
#include <time.h>

using namespace std;

int main()
{
    const int upper_bound = 100;
    const int lower_bound = 1;

    time_t system_time = time(0);    

    default_random_engine e(system_time);
    uniform_int_distribution<int> u(lower_bound, upper_bound);

    cout << '#' << '\t' << "system time" << endl
         << "-------------------" << endl;

    for (int counter = 1; counter <= 5; counter++)
    {
        int secret = u(e);
        cout << secret << '\t' << system_time << endl;
    }   

    system("pause");
    return 0;
}

3
什么是sizeof(time_t)对sizeof(default_random_engine::result_type)?
— Mark Ransom 2015年

3
请注意,default_random_engine这两个平台完全不同。
— TC

1
它仍然可以是随机BTW。
— 亚历克·蒂尔

5
每个程序员是否都会经历一个他们认为时间是一个好的随机数生成器种子的阶段?
— OldFart 2015年

6
@OldFart是的,它称为学术界。
— 凯西

Answers:


141

这是怎么回事:

  • default_random_engine在libstdc ++(GCC的标准库)中minstd_rand0,是,这是一个简单的线性同余引擎:

    typedef linear_congruential_engine<uint_fast32_t, 16807, 0, 2147483647> minstd_rand0;
  • 该引擎生成随机数的方式是x i + 1 =(16807x i + 0)mod 2147483647。

  • 因此,如果种子相差1,那么大多数情况下,第一个生成的数字相差16807。

  • 该生成器的范围是[1,2147483646]。libstdc ++ uniform_int_distribution将其映射到[1,100]范围内的整数的方式本质上是这样的:生成一个number n。如果该数字不大于2147483600,则返回(n - 1) / 21474836 + 1; 否则,请尝试使用新号码。

    很容易看出,在大多数情况下,n在此过程中,两个仅相差16807的s在[1,100]中将产生相同的数字。实际上,人们希望生成的数字大约每21474836/16807 = 1278秒或21.3分钟增加1,这与您的观察非常吻合。

MSVC default_random_engine是mt19937,没有这个问题。


36
我想知道是什么让GCC标准库的开发人员选择了如此可怕的默认值。
— CodesInChaos

13
@CodesInChaos我不知道这是否相关,但MacOS / iOS工具链也使用相同的可怕随机引擎,从而使rand()%7始终返回0
— phuclv 2015年

7
@LưuVĩnhPhúc不修复rand()是可以理解的(这是绝望的废话)。为新事物使用狗屎层PRNG是不可原谅的。我什至认为这是一个标准违规,因为该标准要求“为相对随意,不熟练和/或轻量级使用提供至少可接受的引擎行为”。该实现不提供此功能,因为即使在像您的rand % 7示例这样的琐碎用例中,它也会灾难性地失败。
— CodesInChaos

2
@CodesInChaos为什么不rand()完全解决固定问题?仅仅是因为没有人想到过这样做吗?
— user253751

2
@immibis该API是如此糟糕,以至于您最好通过独立的替代程序来解决所有问题。1)替换算法将是一项重大突破,因此您可能需要对旧程序进行兼容性切换。2)的种子srand太小,无法轻易生成独特的种子。3)返回一个整数,该整数具有实现定义的上限,调用者必须以某种方式将其减小为所需范围内的数字,如果正确完成,则比为使用合理的API编写替代方法做的工作更多rand()。4)使用全局可变状态
— CodesInChaos

30

该std::default_random_engine是实现定义。使用std::mt19937或std::mt19937_64代替。

此外std::time,ctime功能不是很准确,请改用<chrono>标头中定义的类型:

#include <iostream>
#include <random>
#include <chrono>

int main()
{
    const int upper_bound = 100;
    const int lower_bound = 1;

    auto t = std::chrono::high_resolution_clock::now().time_since_epoch().count();

    std::mt19937 e;
    e.seed(static_cast<unsigned int>(t)); //Seed engine with timed value.
    std::uniform_int_distribution<int> u(lower_bound, upper_bound);

    std::cout << '#' << '\t' << "system time" << std::endl
    << "-------------------" << std::endl;

    for (int counter = 1; counter <= 5; counter++)
    {
        int secret = u(e);

        std::cout << secret << '\t' << t << std::endl;
    }   

    system("pause");
    return 0;
}

3
播种伪随机变量生成器时是否需要使用更准确的时间?也许这是幼稚的,但是如果引入了熵,那似乎很不准确。(除非你的意思是它不太准确,从而导致重大潜在较少的种子。)
— 纳特

15
我只是建议使用std::random_device而不是current_time为您的随机生成器添加种子。请检查有关随机的任何cppreference示例。
— Aleksander Fular 2015年

5
如果您不希望任何人猜测您的种子(并因此重现您的序列),那么降低精度就等于提高随机性。让我们走到极端:将种子四舍五入到第二天(或一年?)->猜测很容易。使用飞秒精度->做很多猜测...
— linac

2
@ChemicalEngineer的粒度ctime为1秒。std::chrono实现的粒度是用户定义的,默认为std::high_resolution_clock(在Visual Studio中为typedef std::steady_clock)纳秒,但可以选择更小的度量,因此更为精确。
— 凯西2015年

2
@linac如果您想要加密属性,则可以使用适当的prng(此答案中没有使用)。当然,不管承诺的精度如何,基于时间的种子也是不可能的。
— 克苏鲁2015年

-2

在Linux中,随机函数不是概率意义上的随机函数,而是伪随机数生成器。它用种子腌制,并基于该种子产生的数量是伪随机且均匀分布的。Linux方式的优势在于,在使用来自种群的信息进行某些实验的设计中,可以测量已知输入信息调整后的重复实验。当最终程序准备好进行实际测试时,可以通过要求用户移动鼠标,将鼠标移动与一些击键混合以及自最后通电。

Windows随机数种子是从鼠标,键盘,网络和一天中的时间数字的集合中获得的。这是不可重复的。但是,如果如上所述,该盐值可以重置为已知种子,则该盐值将参与实验设计。

哦,是的,Linux有两个随机数生成器。一个是默认值,是32位模,另一个是64位模。您的选择取决于精度需求和希望用于测试或实际使用的计算时间。


5
我不确定您为什么要谈论种子生成算法。OP显然将系统时间用作种子。另外,您是否可以添加一些引用collection of mouse, keyboard, network and time of day numbers
— 默认语言环境2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.