C++中的随机数

历史悠久的rand()

咱们会使用从C继承而来的 int rand(); 函数做为随机数发生器,该随机数的范围为[0, RAND_MAX],其中 RAND_MAX 是 <stdlib.h> 中经过宏定义的一个常量,在C和C++标准中,均为“不低于32767的正整数”,大部分编译器都使用了32767做为 RAND_MAXios

若是咱们要使用它的话,须要注意的是,这个rand虽然随机性较好,但通常的用法 rand() % RANGE 很容易形成不均匀的随机结果,例如当 #define RAND_MAX 32767 RANGE = 1000; 时,[0, 767]范围内的几率要比[768, 999]范围内的几率大,这是由于随机数的区间[0, 32767]中最后的那部分[32000, 32767],因为缺乏了[32768, 32999],故 rand() % RANGE 落在[0, 767]的几率要更大。git

解决方式也很简单,先将随机结果转化为浮点型,再按比例缩放为整形: int(((double)rand() / (RAND_MAX + 1)) * RANGE) ,这个方法能够解决上面的问题,但因为计算过程极可能形成精度损失,最终又会带来新的不平均。
算法

为了避免老是生成一样的随机序列,通常会使用 void srand(unsigned int seed); 初始化随机数发生器,若是咱们要生成固定序列的随机数,使用一样的随机数种子就好,若是须要更加的随机一些,使用 <ctime> 中的 clock_t clock(); 是个不错的选择,由于它的返回值是系统时间的整型表达,精度为毫秒,能达到比较强的随机性数据结构

C++11新增的随机函数

基于 int rand(); 的局限性考虑,C++11中新增了一堆随机数引擎、随机数分布器、随机数适配器,可根据指定的规则生成几率密度函数或离散几率分布,也就是下标为浮点数或整数的随机数。dom

  • 分布器

 里面有不少实用的随机数分布器,用于把原始随机数按照指定分布进行映射,来生成所须要的特定随机序列:函数

  •  uniform_int_distribution 均匀分布的随机整型(和上面的 int rand(); 功能相似)
  •  uniform_real_distribution 均匀分布的随机浮点型
  •  bernoulli_distribution 随机布尔型
  •  binomial_distribution 二项分布的随机整型
  •  binomial_distribution 几何分布的随机整型
  •  exponential_distribution 指数分布的随机整型
  • ......

等等,都是统计方面经常使用的函数,同时这些分布器也均为模版,虽然整型和浮点型的模版不能互相通用,但整型可使用包括从 short 到 unsigned long long 在内的全部原始整型,浮点型也可使用包括 float 、 double 和 long double 等全部原始浮点型。具体完整的分布器列表和使用方法能够查看RandomNumberDistributionui

  • 引擎

上面的这些分布器实际上是将原始的随机数以定制好的分布状况映射而来的,而C++11也提供了不少种随机数生成引擎,如最经常使用的 default_random_engine ,其定义为 std::linear_congruential_engine<std::uint_fast32_t, 16807, 0, 2147483647> ,而 linear_congruential_engine 的数据结构模版为:spa

template<
    class UIntType, // 指定数据类型
    UIntType a, 
    UIntType c, 
    UIntType m // 生成公式:x[i+1] = (a * x[i] + c) mod m
> class linear_congruential_engine;

这个公式极其简单,并且随机性在某些状况下并不平均,因此像我这种强迫症可能并不知足,因而C++11也提供了不少其余的引擎可供选择,好比说采用了马特赛特旋转演算法(Mersenne Twister)的 mersenne_twister_engine :3d

template<
    class UIntType, 
    size_t w, size_t n, size_t m, size_t r, // 参数含义未知,待补充
    UIntType a, size_t u, UIntType d, size_t s,
    UIntType b, size_t t,
    UIntType c, size_t l, UIntType f
> class mersenne_twister_engine;

通常使用 mt19937 ,也就是code

std::mersenne_twister_engine<std::uint_fast32_t, 32, 624, 397, 31,
                             0x9908b0df, 11,
                             0xffffffff, 7,
                             0x9d2c5680, 15,
                             0xefc60000, 18, 1812433253>

 该算法(使用上述参数时)获取的随机数,周期长达219937-1,也就是说,生成了219937-1个数后,才会开始生成如出一辙的序列,且分布极其均匀,可算是最经常使用的随机数生成算法之一。

另外还有64位版本的 mt19937_64 :

std::mersenne_twister_engine<std::uint_fast64_t, 64, 312, 156, 31,
                             0xb5026f5aa96619e9, 29,
                             0x5555555555555555, 17,
                             0x71d67fffeda60000, 37,
                             0xfff7eee000000000, 43, 6364136223846793005>

这种算法算是基于有限二进制字段上的矩阵线性再生,可在生成高质量随机数的同时,保证很高的效率,通常来讲能够知足绝大部分人的须要了。

下面是这个算法的伪代码(某呼上找的,来源未知):

 另外还有一种滞后Fibonacci类型的subtract with carry(直译为带进位减法?)算法引擎 subtract_with_carry_engine :

template<
    class UIntType, 
    size_t w, size_t s, size_t r // 需知足:0<s<r,0<w
> class subtract_with_carry_engine;

下面是张来自Wikipedia的算法说明,细节就不作介绍了(由于我也不懂)

{\displaystyle x(i)=(x(i-S)-x(i-R)-cy(i-1))\ {\bmod {\ }}M}

where{\displaystyle cy(i)={\begin{cases}1,&{\text{if }}x(i-S)-x(i-R)-cy(i-1)<0\\0,&{\text{otherwise}}\end{cases}}}.

其中:

 上述引擎都是使用必定的算法生成的伪随机数序列,而 random_device 则是生成非肯定随机数的均匀分布整数随机数生成器,是使用计算机硬件来生成随机序列(若是硬件不支持或不可用,或编译器不支持,则生成的依旧会是伪随机数序列)的引擎,用法与其余随机数引擎相似。

PS:Unix/Linux下的g++会经过调用 /dev/urandom 获取真随机序列,但Windows下的mingw、mingw-w6四、VS都没实现真随机((‵□′)) 

  • 引擎适配器

引擎适配器就是将其余引擎或引擎适配器生成的随机数做为输入,按照必定的规则,再变换生成一次,简单地说,就是随机数的二次生成,好比:

生成指定二进制位数的随机数的 independent_bits_engine :

template<
    class Engine, 
    std::size_t W, // 表示指定的二进制位数,需知足:0<=W<=std::numeric_limits<UIntType>::digits(即UIntType的总位数)
    class UIntType
> class independent_bits_engine;

会随机丢弃(可指定频率的)原生成序列中随机数的 discard_block_engine :

template<
    class Engine, 
    size_t P, size_t R // 每P个Engine生成的随机数,随机保留R个,其他丢弃,需知足:0<R<=P
> class discard_block_engine;

打乱原生成序列顺序的 shuffle_order_engine :

template<
    class Engine, 
    std::size_t K // 维护一个K大小的表并预装入K个随机数,每次生成从表中随机取出一个数,并把Engine生成的随机数替换入表中
> class shuffle_order_engine;

须要特别注意的是, random_device 虽然也是一种随机数引擎,但其并非 RandomNumberEngine ,也就是不能做为引擎适配器中的基础引擎。因此,若是没法实现真随机,那么这货就没用了……

简单的例子

下面是一段生成总大小为1GB的随机大数并二进制形式输出至文件的程序,使用了 std::mt19937_64 做为基础引擎, std::discard_block_engine<std::mt19937_64, 20, 15> 做为引擎适配器, std::uniform_int_distribution<unsigned> 做为分布器:

#include <fstream>
#include <random>
#include <iostream>

const unsigned long long DATA_SIZE = 1073741824;  // shoud be times of DATA_SINGLE_SIZE
const unsigned long long DATA_SINGLE_SIZE = 128;  // shoud be times of 8
const unsigned long long DATA_COUNT = DATA_SIZE / DATA_SINGLE_SIZE;

typedef unsigned number_type;

struct Data {
    number_type v[DATA_SINGLE_SIZE / sizeof(number_type)];
};

int main() {
    std::ofstream outfile;
    outfile.open("text_data.dat", std::ios_base::binary);
    if (outfile.is_open()){
        std::discard_block_engine<std::mt19937_64, 20, 15> generator_random_discard;
        std::uniform_int_distribution<number_type> distribution;

        Data temp;

        outfile.write((char*)&DATA_SIZE, sizeof(DATA_SIZE));
        outfile.write((char*)&DATA_SINGLE_SIZE, sizeof(DATA_SINGLE_SIZE));
        for (int i = 0; i < DATA_COUNT; i++) {
            for (auto v = std::begin(temp.v); v != std::end(temp.v); v++) {
                *v = distribution(generator_random_discard);
            }
            outfile.write((char*)&temp, sizeof(Data));
        }

        outfile.close();
    }
    else {
        std::cerr << "Can\'t create the file [test_data.dat]!" << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

补充

时间函数time()和clock()也有了C++11的版本,具体使用方法下次再细说,下面是一个在stackoverflow.com上的代码:

#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';
}
相关文章
相关标签/搜索