【问题标题】:<random> generates same number in Linux, but not in Windows<random> 在 Linux 中生成相同的数字,但在 Windows 中不生成
【发布时间】:2015-12-20 06:13:48
【问题描述】:

下面的代码旨在生成区间 [1,100] 中的五个伪随机数的列表。我用time(0)default_random_engine 播种,它在unix time 中返回系统时间。当我使用 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;
}

【问题讨论】:

  • sizeof(time_t)sizeof(default_random_engine::result_type) 是什么?
  • 请注意,default_random_engine 在这两个平台上完全不同。
  • 它仍然可以是随机的 BTW。
  • 是否每个程序员都经历过他们认为时间是一个很好的随机数生成器种子的阶段?
  • @OldFart 是的,这叫学术界。

标签: c++ linux windows gcc visual-studio-2013


【解决方案1】:

std::default_random_engine 是实现定义的。请改用std::mt19937std::mt19937_64

另外std::timectime函数不是很准确,改用&lt;chrono&gt;头中定义的类型:

#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;
}

【讨论】:

  • 在为伪随机变量生成器播种时是否需要使用更准确的时间?也许这是幼稚的,但如果它引入熵,感觉就像不准确可能几乎是可取的。 (除非你的意思是它不那么精确,因此导致潜在种子的数量大大减少。)
  • 我只是建议使用 std::random_device 而不是 current_time 来播种随机生成器。请检查任何关于 Random 的 cppreference 示例。
  • 如果您不想让任何人猜测您的种子(并因此复制您的序列),那么较少的精度与较多的随机性是不一样的。让我们走极端:将你的种子循环到第二天(或一年?) -> 猜测很容易。使用飞秒精度 -> 很多猜测要做......
  • @ChemicalEngineer ctime 的粒度为 1 秒。 std::chrono 实现的粒度是用户定义的,默认为 std::high_resolution_clock(在 Visual Studio 中它是 std::steady_clock 的 typedef),纳秒,但可以选择更小的测量值,因此更精确。
  • @linac 如果您想要加密属性,您将使用适当的 prng(此答案中未使用)。当然,无论承诺的精度如何,基于时间的种子也是不可能的。
【解决方案2】:

这是怎么回事:

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

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

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

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

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

MSVC的default_random_enginemt19937,不存在这个问题。

【讨论】:

  • 我想知道是什么让 GCC 标准库的开发人员选择了如此可怕的默认值。
  • @CodesInChaos 我不知道是否与此有关,但 MacOS/iOS 工具链也使用相同的可怕随机引擎,使rand() % 7 always return 0
  • @LưuVĩnhPhúc 不修复 rand() 在某种程度上是可以理解的(这是毫无希望的遗留废话)。将垃圾级 PRNG 用于新事物是不可原谅的。我什至认为这是违反标准的,因为该标准要求“至少为相对随意、不熟练和/或轻量级的使用提供可接受的引擎行为”。这个实现没有提供,因为即使对于像 rand % 7 示例这样的琐碎用例,它也会发生灾难性的失败。
  • @CodesInChaos 为什么不修复rand() 完全可以理解?仅仅是因为没有人可能想过这样做吗?
  • @immibis API 太坏了,最好用一个独立的替代品来解决所有问题。 1) 替换算法将是一项重大更改,因此您可能需要对旧程序进行兼容性切换。 2)srand的种子太小,不容易生成唯一的种子。 3) 它返回一个具有实现定义的上限的整数,调用者必须以某种方式将其减少到所需范围内的一个数字,如果正确完成,这比使用rand() 的理智 API 编写替换工作要多 4) 它使用全局可变状态
【解决方案3】:

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

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

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

【讨论】:

  • 我不知道你为什么在谈论种子生成算法。 OP 显然使用系统时间作为种子。另外,您能否添加一些对collection of mouse, keyboard, network and time of day numbers 的引用
猜你喜欢
  • 2010-12-23
  • 1970-01-01
  • 1970-01-01
  • 2021-12-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-17
相关资源
最近更新 更多