c++中的统一随机数生成器

Uniform random number generator in c++

本文关键字:随机数生成器 c++      更新时间:2023-10-16

我正试图用c++TR1在c++中产生真正的随机数。然而,当再次运行我的程序时,它会产生相同的随机数。代码如下。

每次跑步我都需要真正的随机数,尽可能随机。

std::tr1::mt19937 eng;  
std::tr1::uniform_real<double> unif(0, 1);
unif(eng);

您必须使用种子初始化引擎,否则将使用默认种子:

eng.seed(static_cast<unsigned int >(time(NULL)));

然而,如果没有额外的输入,true随机性是在确定性机器上无法实现的。每个伪随机数生成器在某种程度上都是周期性的,这是非确定性数字所没有的。例如,std::mt19937具有219937-1次迭代的周期。真正的随机性很难实现,因为你必须监控一些看起来不确定的东西(用户输入、大气噪声)。请参阅Jerry和Handprint的答案。

如果你不想要基于时间的种子,你可以使用std::random_device,如emsr的答案所示。您甚至可以使用std::random_device作为生成器,这是仅使用标准库方法最接近真实随机性的生成器。

这些是伪随机数生成器。他们永远不会产生真正的随机数。为此,您通常需要特殊的硬件(例如,通常需要测量热二极管中的噪声或放射源的辐射)。

为了在不同的运行中从伪随机生成器中获得不同的序列,通常会根据当前时间对生成器进行种子设定。

不过,这会产生相当可预测的结果(即,其他人可以很容易地计算出你使用的种子。如果你需要防止这种情况发生,大多数系统都会提供一些至少相当随机的数字来源。在Linux上,/dev/random,在Windows上,CryptGenRandom

不过,后者往往相当慢,所以你通常想把它们作为种子,而不仅仅是从中检索所有的随机数。

如果您想要真正的硬件随机数,则标准库通过random_device类提供访问权限:

我用它来播种另一个生成器:

#include <random>
...
  std::mt19937_64 re;
  std::random_device rd;
  re.seed(rd());
...
  std::cout << re();

如果您的硬件具有/dev/urandom或/dev/random,则将使用此选项。否则,该实现可以自由使用其伪随机生成器之一。在G++上,19937被用作后备方案。

我很确定tr1也有这一点,但正如其他人所指出的那样,我认为在这一点上最好使用std C++11实用程序。

Ed

这个答案是一个wiki我正在.NET中开发一个库和示例,可以随意添加任何语言的库和示例。。。

如果没有外部的"随机"输入(例如监测街道噪音),作为一台确定性机器,计算机就无法生成真正的随机数:随机数生成。

由于我们大多数人没有资金和专业知识来利用特殊设备提供混乱输入,因此有一些方法可以利用操作系统、任务调度程序、流程管理器和用户输入(例如鼠标移动)的不可预测性,以生成改进的伪随机性。

不幸的是,我对C++TR1的了解还不够,不知道它是否有能力做到这一点。

编辑

正如其他人所指出的,通过用不同的输入为RNG播种,你会得到不同的数字序列(最终会重复,所以它们不是真正随机的)。因此,你有两个选择来提高你的一代:

根据系统的运行方式,定期用某种混沌输入重新设置RNG,或者使RNG的输出不可靠

前者可以通过创建算法来实现,该算法通过检查系统环境来明确地产生种子。这可能需要设置一些事件处理程序、委托函数等。

后者可以通过糟糕的并行计算实践来实现:即设置许多RNG线程/进程以"不安全的方式"竞争,以创建每个后续随机数(或数字序列)。这会从系统上的活动总数中隐含地添加混乱,因为当调用"GetNext()"类型的方法时,每分钟的事件都会影响哪个线程的输出最终被写入并最终被读取。以下是.NET 3.5中概念的粗略证明。注意两件事:1)即使RNG每次都以相同的编号播种,也不会创建24个相同的行;2) 在改进随机数生成时,性能受到了显著的影响,资源消耗也明显增加:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace RandomParallel
{
    class RandomParallel
    {
        static int[] _randomRepository;
        static Queue<int> _randomSource = new Queue<int>();
        static void Main(string[] args)
        {
            InitializeRepository(0, 1, 40);
            FillSource();
            for (int i = 0; i < 24; i++)
            {
                for (int j = 0; j < 40; j++)
                    Console.Write(GetNext() + " ");
                Console.WriteLine();
            }
            Console.ReadLine();
        }
        static void InitializeRepository(int min, int max, int size)
        {
            _randomRepository = new int[size];
            var rand = new Random(1024);
            for (int i = 0; i < size; i++)
                _randomRepository[i] = rand.Next(min, max + 1);
        }
        static void FillSource()
        {
            Thread[] threads = new Thread[Environment.ProcessorCount * 8];
            for (int j = 0; j < threads.Length; j++)
            {
                threads[j] = new Thread((myNum) =>
                    {
                        int i = (int)myNum * _randomRepository.Length / threads.Length;
                        int max = (((int)myNum + 1) * _randomRepository.Length /     threads.Length) - 1;
                        for (int k = i; k <= max; k++)
                        {
                            _randomSource.Enqueue(_randomRepository[k]);
                        }
                    });
                threads[j].Priority = ThreadPriority.Highest;
            }
            for (int k = 0; k < threads.Length; k++)
                threads[k].Start(k);
        }
        static int GetNext()
        {
            if (_randomSource.Count > 0)
                return _randomSource.Dequeue();
            else
            {
                FillSource();
                return _randomSource.Dequeue();
            }
        }
    }
}

只要在生成过程中有用户输入/交互,这种技术就会生成一个不可破解、不重复的"随机"数字序列。在这种情况下,知道机器的初始状态不足以预测结果。

下面是一个播种引擎的例子(使用C++11而不是TR1)

#include <chrono>
#include <random>
#include <iostream>
int main() {
    std::mt19937 eng(std::chrono::high_resolution_clock::now()
                                          .time_since_epoch().count());
    std::uniform_real_distribution<> unif;
    std::cout << unif(eng) << 'n';
}

按照当前时间播种是相对可预测的,可能不应该这样做。以上至少不会将你限制在每秒一个可能的种子,这是非常可预测的。

如果你想从/dev/random之类的东西而不是当前时间播种,你可以这样做:

std::random_device r;
std::seed_seq seed{r(), r(), r(), r(), r(), r(), r(), r()};
std::mt19937 eng(seed);

(这取决于您的标准库实现。例如,libc++默认使用/dev/urandom,但在VS11中random_device是确定的)

当然,你从mt19937中得到的任何东西都不能满足你对"真随机数"的要求,我怀疑你真的不需要真随机性。