C、Java、C++中的随机数机制解析

Java随机数

 

Random类中三个同余表达式常量的剖析

 

private static final long multiplier = 0x5DEECE66DL;

private static final long addend = 0xBL;

private static final long mask = (1L << 48) - 1;

 

参考同余公式 (oldseed * multiplier + addend) & mask

 

@mask的取值: (1L << 48) == 2^48==281474976710656

mask取得较大的数字的时候,同余序列的周期就越长,这样随机效果将越好,一般mask会是2^m次方,m为计算机的位长,这里m取值为48,至于后面-1的原因:如果不减1,产生的随机序列的低比特位的随机性不好!

 

@multiplier 的取值:0x5DEECE66D==25214903917‬

multiplier 应该尽可能大,因为大的multiplier产生的同余序列具有较大的同余周期,但是同余周期大了,随机性将降低!

 

@addend 的取值:0xB==11

当addend 等于0时,有可能产生周期为mask的周期,因此addend 不能为0!

另外,当mask和multiplier 取得相对合理时,此时addend没有特别的要求,但是要求addend 和 mask一定要互质(11和281474976710655),因此addend 一般为1或11

 

什么是种子

在同余发生器中,种子是:the starting value

什么是种子——种子是要生长的(比如长成树),这在next函数中原形毕露,因为next的主要目的就是更新种子,并返回一个随机序列(即种子为Xn,返回序列为Xn+1(n+1为下标)),那么我们在创建Random对象的时候,其实就是设置了一个种子(即X0初始值),看源码:

public Random(long seed) {

    if (getClass() == Random.class)

        this.seed = new AtomicLong(initialScramble(seed));

    else {

        // subclass might have overriden setSeed

        this.seed = new AtomicLong();

        setSeed(seed);

    }

}

其中关键地方已经用红色加粗,我们再转到initialScrambleAtomicLong构造函数去看看:

 

@AtomicLong构造函数

private volatile long value;

/**

 * Creates a new AtomicLong with the given initial value.

 *

 * @param initialValue the initial value

 */

public AtomicLong(long initialValue) {

    value = initialValue;

}

 

这下我们看到了,我们传递给Random的种子,被保存在了AtomicLongvalue变量中,并且value变量被特意声明为volatile ,这意味着value变量天生注定被修改!

只是这里有个小插曲就是我们传递进去的种子被修改过了,这是修改代码:

@initialScramble定义

private static long initialScramble(long seed) {

    return (seed ^ multiplier) & mask;

}

 

Random类核心方法之一next的剖析

//1,通过同余公式计算并设置新的种子

//2,返回新的种子的高比特位

//3,next会产生一个线性同余序列

protected int next(int bits) {

    long oldseed, nextseed;

    AtomicLong seed = this.seed;//AtomicLong seed是指向this.seed的

    do {

//返回种子

        oldseed = seed.get();

//获取新种子保存在nextseed中

        nextseed = (oldseed * multiplier + addend) & mask;        

} while (!seed.compareAndSet(oldseed, nextseed));//将nextseed 设置为新种子更新种子

/*@compareAndSet函数解析:

以下是compareAndSet的定义以及官方说明:

@定义:

public final boolean compareAndSet(long expectedValue, long newValue) {

    return U.compareAndSetLong(this, VALUE, expectedValue, newValue);

}

@官方说明:

public final boolean weakCompareAndSet(long expect,

                                   long update)

Atomically sets the value to the given updated valueif the current value == the expected value.

May failspuriously and does not provide ordering guarantees, so isonly  rarely an appropriate alternative to compareAndSet.

Parameters:expect - the expected valueupdate - the new valueReturns:true if successful

*/

    return (int)(nextseed >>> (48 - bits));//去掉低位比特,保留高位比特为了保证良好的随机性

}

 

随机是怎么发生的?随机的本来面目

经由上面的分析,我们知道,所谓的随机就是有一个初始值(种子),然后利用同余算法产生一个序列,这个序列的周期尽可能的长,序列中各元素的随机性尽可能高(产生一个优质序列),因此我们每一次调用Random成员方法(例如:nextInt()),我们都是在扩展这个同余序列,以获得下一个同余序列元素!因此我们以为获取到的“随机值”只是这个序列中的一个元素罢了!

那么问题来了,因为我们Random的三个同余表达式常量已经被钉死不能改变了(事实上,他们只作用于使产生的随机数效率更高!):

 

private static final long multiplier = 0x5DEECE66DL;

private static final long addend = 0xBL;

private static final long mask = (1L << 48) - 1;

 

那么能够改变我们产生不同的同余序列的因素就是种子(初始值)了:不同的种子应用于同余算法产生不同的序列!

我们尽量按照“那是一个序列”而不是“那真的是产生的随机数”来理解java随机数机制比较清晰点!)

 

我们看看是不是这样呢?

 

Random构造函数探析:

@无参构造

public Random() {

    this(seedUniquifier() ^ System.nanoTime());

}

首先,我们看到默认构造函数也调用了有参的构造函数:

@有参构造

public Random(long seed) {

    if (getClass() == Random.class)

        this.seed = new AtomicLong(initialScramble(seed));

    else {

        // subclass might have overriden setSeed

        this.seed = new AtomicLong();

        setSeed(seed);

    }

}

我们知道,我们在使用Random的时候有时候会使用new Random(value)的形式,就是调用的上面这个函数!并且此时不管程序何时运行,只要value相同,那么所有的具有value构造实参Random都只会产生相同的随机数(相同的同余序列)!为社么呢?我们从无参构造函数来反验证:

因为无参形如new Random()形式产生的Random对象都是具有“真”随机的随机发生对象(这里的真指不与此对象创建时间相同的所有对象、或者两次启动程序,都将呈现一个不同的同余序列),因此:

 

Random r1=new Ramdom();

Random r2=new Ramdom();

 

上述两个Random对象r1、r2是不同的随机数发生器!

这要追究于Ranodm无参构造——因为它调用Random有参构造的时候提供了不同的种子:

@System.nanoTime()

此函数定义:

public static native long nanoTime();

对于旧版本,这里原本是System.currentTimeMillis(),但是现在换为了System.nanoTime(),究其原因,是因为效能更好,为什么更好呢?让我们分析一个System.nanoTime()函数:

nanoTime函数返回一个(时间段)duration,指从UNIX系统时钟起始点开始到现在的纳秒秒数,这个起始点(epoch)为1970年1月1日晚上12点整(不绝对),纳秒为此返回duration的时间单位,因此我们就能知道为什么新版本用System.nanoTime()替换SSystem.currentTimeMillis()更好了——因为对于在很短的时间间隔内提供两个不同的种子有保障!

 

@seedUniquifier定义:

private static long seedUniquifier() {

    // L'Ecuyer, "Tables of Linear Congruential Generators of

    // Different Sizes and Good Lattice Structure", 1999

    // 

    for (;;) {

        long current = seedUniquifier.get();

        long next = current * 1181783497276652981L;

        if (seedUniquifier.compareAndSet(current, next))

            return next;

    }

}

对于seedUniquifier我们可以理解其为先对System.nanoTime()获得的数值进行处理,之后再作为随机种子的作用!

 

 

Random类的用法

至于Random成员函数的使用方法,读者可自行到网上搜索用法(主要的是明白其原理!知其所以然,则知其然亦易!),这里列出Random的常用方法,读者可自行查阅Random源码阅览(推荐):

 

a 、public boolean nextBoolean()

该方法的作用是生成一个随机的boolean值,生成true和false的值几率相等,也就是都是50%的几率。

 

b 、public double nextDouble()

该方法的作用是生成一个随机的double值,数值介于[0, 1.0)之间,这里中括号代表包含区间端点,小括号代表不包含区间端点,也就是0到1之间的随机小数,包含0而不包含1.0。

 

c 、public int nextInt()

该方法的作用是生成一个随机的int值,该值介于int的区间,也就是 - 2的31次方到2的31次方 - 1之间。

如果需要生成指定区间的int值,则需要进行一定的数学变换,具体可以参看下面的使用示例中的代码。

 

d 、public int nextInt(int n)

该方法的作用是生成一个随机的int值,该值介于[0, n)的区间,也就是0到n之间的随机int值,包含0而不包含n。

如果想生成指定区间的int值,也需要进行一定的数学变换,具体可以参看下面的使用示例中的代码。

 

e 、public void setSeed(long seed)

该方法的作用是重新设置Random对象中的种子数。设置完种子数以后的Random对象和相同种子数使用new关键字创建出的Random对象相同。

 

Random类使用示例

使用Random类,一般是生成指定区间的随机数字,下面就一一介绍如何生成对应区间的随机数字。以下生成随机数的代码均使用以下Random对象r进行生成:

Random r = new Random();

 

a 、生成[0, 1.0)区间的小数

double d1 = r.nextDouble();

直接使用nextDouble方法获得。

 

b、生成[0, 5.0)区间的小数

double d2 = r.nextDouble() * 5;

因为nextDouble方法生成的数字区间是[0, 1.0),将该区间扩大5倍即是要求的区间。

同理,生成[0, d)区间的随机小数,d为任意正的小数,则只需要将nextDouble方法的返回值乘以d即可。

 

c、生成[1, 2.5)区间的小数  [n1,n2]

double d3 = r.nextDouble() * 1.5 + 1; 【也就是 r.nextDouble() * (n2 - n1) + n1】

生成[1, 2.5)区间的随机小数,则只需要首先生成[0, 1.5)区间的随机数字,然后将生成的随机数区间加1即可。

同理,生成任意非从0开始的小数区间[d1, d2)范围的随机数字(其中d1不等于0),则只需要首先生成[0, d2 - d1)区间的随机数字,然后将生成的随机数字区间加上d1即可。

 

d、生成任意整数

int n1 = r.nextInt();

直接使用nextInt方法即可。

 

e、生成[0, 10)区间的整数

int n2 = r.nextInt(10);

n2 = Math.abs(r.nextInt() % 10);

以上两行代码均可生成[0, 10)区间的整数。

 

(未完待续......C/C++中随机数的机制)

(接着写)

 

在C语言中使用随机数

 

void GenerateRandom(){

//设置变化的种子:time为从UNIX的epoch到现在经过的秒数

srand((unsigned)time(NULL));

for (int i = 0; i < 10; ++i){

//如果没有srand((unsigned)time(NULL))这一步,那么我们每次调用GenerateRandom生成的随机序列将完全一样

cout << rand() << " ";

}

cout << endl;

}

 

上面的例子我们在生成随机序列之前先设置了变化的种子,因此我们期望可能每次调用GenerateRandom都能生成不同的随机数序列,然而有些情况确实如此,但是有些情况却显得很糟糕,糟糕如下:

1,例如我们希望如下调用GenerateRandom函数期望得到不同的随机序列(因此你或许觉得每次调用函数GenerateRandom都以(unsigned)time(NULL)设置了变化的种子),然而情况却非我们所料:

 

void main(){

GenerateRandom();

GenerateRandom();

GenerateRandom();

}

 

程序运行结果:

15364 15859 7106 20334 9554 2143 19129 13338 19927 2416

15364 15859 7106 20334 9554 2143 19129 13338 19927 2416

15364 15859 7106 20334 9554 2143 19129 13338 19927 2416

 

看到这样的结果,你是不是怀疑我们设置变化的种子没有起效,让我们再来验证:

 

void main(){

GenerateRandom();

GenerateRandom();

GenerateRandom();

Sleep(1000);//延时一秒

GenerateRandom();

}

 

程序运行结果:

15632 12493 30176 27445 3381 29223 12206 2678 30390 22562

15632 12493 30176 27445 3381 29223 12206 2678 30390 22562

15632 12493 30176 27445 3381 29223 12206 2678 30390 22562

15635 23241 15272 18741 13696 381 15319 32518 14133 16414

 

可以看到,最后一行输出确实是不同的随机序列,因为我们延时了一秒,而srand((unsigned)time(NULL))确实给我们设置了不同的种子!

因此,我们或许希望变化的种子的设置能更精确一点,例如以毫秒计数:

 

void GenerateRandom(){

SYSTEMTIME st = { 0 };  

GetLocalTime(&st);  //获取当前时间 可精确到ms

//设置变化的种子:time为从UNIX的epoch到现在经过的秒数

srand((unsigned)st.wMilliseconds);

for (int i = 0; i < 10; ++i){

//如果没有srand((unsigned)time(NULL))这一步,那么我们每次调用GenerateRandom生成的随机序列将完全一样

cout << rand() << " ";

}

cout << endl;

}

 

此时输出结果:

1011 32248 3577 29880 2406 2314 18371 11925 15704 728

1050 30157 21339 23728 27877 16667 22953 9566 17235 25255

1067 18363 12356 12972 13914 3533 5747 27698 1489 27283

 

虽然达到了目的,但是我们将分析rand/srand的其他不足,而这也是最主要的:

(至于上面srand可以精确到微妙级甚至纳秒级,但这在C/C++中是相当麻烦的一件事,JAVA在这方面给出了一个非常简洁的方法——nanoTime

具体可以参考:http://sodino.com/2015/03/20/c-time-micro-nano/

 

2,运用rand/srand我们如果想获取随机浮点值该怎么办?

先来看看我们怎么获取(假设获取0—1之间的随机浮点值):

 

SYSTEMTIME st = { 0 };  

GetLocalTime(&st);  //获取当前时间 可精确到ms

//设置变化的种子:time为从UNIX的epoch到现在经过的秒数

srand((unsigned)st.wMilliseconds);

 

for (int i = 0; i < 10; ++i){

//如果没有srand((unsigned)time(NULL))这一步,那么我们每次调用GenerateRandom生成的随机序列将完全一样

cout << (double)rand() /RAND_MAX<< " ";

}

cout << endl;

其中RAND_MAX被定义为:#define RAND_MAX 0x7fff/32767/2^15,就是rand可以返回的最大值

 

输出结果:

0.00384533 0.0919828 0.367748 0.901944 0.769189 0.595569 0.81988 0.57329 0.92407 0.868374

0.0118412 0.333232 0.981475 0.650258 0.951262 0.182287 0.418622 0.426618 0.235542 0.858455

0.0125126 0.629322 0.797662 0.790735 0.154668 0.0211188 0.0835292 0.801294 0.76281 0.545061

 

似乎达到效果,但是有个很大的BUG,让我们来模拟一下上面的过程:

 

for (int i = 1; i < 10; ++i){

cout << setprecision(5) << (double)i / 11 << " ";

}

cout << endl;

 

输出结果为:

0.090909 0.18182 0.27273 0.36364 0.45455 0.54545 0.63636 0.72727 0.81818

 

从模拟中我们发现,利用这种方法,我们将永远无法得到(0.090909 0.18182)之间的浮点值,其余相邻输出浮点值之间的浮点值也将不会的到,因为如果想要得到就要形如:1.2/11~1.8/11,但是我们的i(也就是随机序列)被限定为了0——RAND_MAX的整数值

 

因此,我们想获取0~1之间的浮点值,但是很大部分浮点值根本获取不到,那我们怎么能将其视为有效的获取浮点值的方法呢?

 

于是,似乎C语言中获取随机数的rand/srand是个糟糕的方法!

——让我们把目光转向C++

 

在C++中使用随机数

 

C++中的随机数是一个相对庞大的体制,要一窥其全貌不是一件易事,这里剖析其主要的实现!

 

先来看一个简单点的示例:

 

#include<iostream>

#include<random>

using namespace std;

 

void main(){

default_random_engine e;

for (int i = 0; i < 10;++i){

cout << e() << " ";

}

cout << endl;

}

 

输出结果如下:

3499211612 581869302 3890346734 3586334585 545404204 4161255391 3922919429 949333985 2715962298 1323567403

 

首先从输出结果可以看到default_random_engine产生随机数的范围更大(实际范围为0~4294967295),其次default_random_engine称作随机数引擎(random_number engines),利用随机数引擎,我们产生了随机数序列!

 

default_random_engin为何物?

 

通过查看源码,我们得到这样一张继承体系图:

其中对每层名称做以下解释:

 

mersenne_twister:(百度解释:Mersenne Twister算法译为马特赛特旋转演算法,是伪随机数发生器之一,其主要作用是生成伪随机数。此算法是Makoto Matsumoto (松本)和Takuji Nishimura (西村)于1997年开发的,基于有限二进制字段上的矩阵线性再生。可以快速产生高质量的伪随机数,修正了古老随机数产生算法的很多缺陷)

 

_Circ_buf:(百度解释:环形缓冲器(ringr buffer),也称作圆形队列(circular queue),循环缓冲区(cyclic buffer),圆形缓冲区(circula buffer),是一种用于表示一个固定尺寸、头尾相连的缓冲区的数据结构,适合缓存数据流

 

mersenne_twister_engine:梅森旋转生成器,这个引擎的应用非常广泛,因为它可以生成非常高质量的随机序列,但存在速度相对较慢的缺点

 

因此,default_random_engine就是一个用特定参数全局特化了的梅森旋转生成器,至于特化参数的选择,目的是为了在通常环境下产生更好的性能!因此我们得知,default_random_engine是一个使用了梅森旋转算法产生随机数的引擎(至于算法的各个参数,这可能需要花很大功夫,这里不做讨论!)

假如我们想配置自己的算法,可以这样做(当然前提是你理解此算法的运作机理):

#include<iostream>
#include<random>
using namespace std;

void GenerateRandom(){
	default_random_engine e;
	for (int i = 0; i < 10; ++i){
		cout << e() << " ";
	}
	cout << endl;
}

void main(){
	//GenerateRandom();
	//GenerateRandom();
	//GenerateRandom();

	//梅森旋转生成器默认的算法配置
	typedef mersenne_twister_engine<unsigned int, 32, 624, 397, 31, 0x9908b0df,
		11, 0xffffffff, 7, 0x9d2c5680, 15, 0xefc60000, 18, 1812433253> DefaultRandom;

	//改动的梅森旋转生成器的算法配置
	typedef mersenne_twister_engine<unsigned int, 32 ,624, 397, 31, 0x9908b0df,
		11, 0xffffffff, 8, 0x9d2c5680, 15, 0xefc60000, 18, 1812433253> ModRandom;

	default_random_engine e;
	for (int i = 0; i < 10; ++i){
		cout << e() << " ";
	}
	cout << endl;

	ModRandom rng;
	for (int i = 0; i < 10; ++i){
		cout << rng() << " ";
	}
	cout << endl;
}

输出结果:

3499211612 581869302 3890346734 3586334585 545404204 4161255391 3922919429 949333985 2715962298 1323567403

1737872029 734432812 1841417318 3884668779 765867109 1697611925 3852403606 3080030753 834825650 1807766114

 

结果显而易见,配置算法的参数不同,产生的随机序列不同,尽管种子相同!是的,种子相同,也就是说default_random_engine的无参构造函数每次都设置相同的种子,下面我们来讨论种子的问题!

 

啊哈,我们在的定义中找到了他的构造函数:

 

static const result_type default_seed = 5489U;

explicit mersenne_twister_engine(result_type _X0 = default_seed)

: _Mybase((unsigned long)_X0, _Dx, _Fx)

{ // construct with default seed

}

 

mersenne_twister_engine(const mersenne_twister_engine& _Right)

{ // construct by copying

*this = _Right;

}

 

mersenne_twister_engine(mersenne_twister_engine& _Right)

{ // construct by copying

*this = _Right;

}

可见mersenne_twister_engine的无参构造使用了缺省参数default_seed,它是一个result_type类型,那它是一个什么类型呢?看源码:

 

@第一处

typedef mersenne_twister<_Ty, _Wx, _Nx, _Mx, _Rx,

_Px, _Ux, _Sx, _Bx, _Tx, _Cx, _Lx> _Mybase;

typedef _Ty result_type;

 

@第二处

template<class _Ty,

size_t _Wx,

size_t _Nx,

size_t _Mx,

size_t _Rx,

_Ty _Px,

size_t _Ux,

_Ty _Dx, // added

size_t _Sx,

_Ty _Bx,

size_t _Tx,

_Ty _Cx,

size_t _Lx,

_Ty _Fx> // added

class mersenne_twister_engine

: public mersenne_twister<_Ty, _Wx, _Nx, _Mx, _Rx,

_Px, _Ux, _Sx, _Bx, _Tx, _Cx, _Lx>

 

@第三处

typedef mersenne_twister_engine<unsigned int, 32, 624, 397, 31, 0x9908b0df,

11, 0xffffffff, 7, 0x9d2c5680, 15, 0xefc60000, 18, 1812433253> mt19937;

通过这三处源码,我们知道result_type的类型就是unsigned int

从上面的分析,我们知道,当我们给设置种子的时候,要保证参数是unsigned int范围!

 

通过上面对mersenne_twister_engine缺省实参的构造函数的分析,我们很容易就能知道怎么给引擎指定变化的种子:

#include<iostream>

#include<random>

#include<time.h>

#include<windows.h>

using namespace std;

 

void GenerateRandom(){

 

SYSTEMTIME st = { 0 };  

GetLocalTime(&st);  //获取当前时间 可精确到ms

//设置变化的种子:time为从UNIX的epoch到现在经过的秒数

default_random_engine e((unsigned int)st.wMilliseconds);

 

for (int i = 0; i < 10; ++i){

cout << e() << " ";

}

cout << endl;

}

 

void main(){

GenerateRandom();

GenerateRandom();

GenerateRandom();

}

 

输出结果为:

1397775013 1724333663 3818866136 47638124 1765486575 2397657817 762935715 1794931627 1036390536 1839039361

1738293281 3886926354 76321241 2380861722 2154849979 2166612048 1043420035 3251042161 1645474461 1918444383

2657240058 2836836797 1686148628 1449704219 786942726 2018998325 3908064961 2700918428 2409262931 1291809066

 

通过上面对mersenne_twister_engine引擎的分析,我们知道此引擎使用了性能优秀的算法产生序列,并且其随机值范围更大,但最亮眼的是引擎和随机分布类的使用以及如何生成非均匀分布的随机数(也是使用随机分布类)!接下来我们继续讨论:

 

随机分布类

 

观察上面的分析,我们发现由mersenne_twister_engine殷勤产生的随机数在一个很大的范围内,但是我们通常都需要某个范围内的随机数(例如1~10),此时将搭配随机分布类来使用引擎!

看个例子:

 

#include<iostream>

#include<random>

#include<time.h>

#include<windows.h>

using namespace std;

 

void GenerateRandom(){

 

SYSTEMTIME st = { 0 };  

GetLocalTime(&st);  //获取当前时间 可精确到ms

//设置变化的种子:time为从UNIX的epoch到现在经过的秒数

default_random_engine e((unsigned int)st.wMilliseconds);

 

uniform_int_distribution<unsigned int> u(0, 9);

for (int i = 0; i < 10; ++i){

cout << u(e) << " ";

}

cout << endl;

}

 

void main(){

GenerateRandom();

GenerateRandom();

GenerateRandom();

}

 

输出结果:

6 4 3 3 8 5 2 3 5 9

3 3 1 7 9 8 3 1 5 4

5 9 4 5 1 6 7 3 6 4

 

这里面有个需要澄清的问题是,我们是否可以直接对从引擎获得的数值取模10来获得0~10之间的随机数呢?例如这样:

 

SYSTEMTIME st = { 0 };  

GetLocalTime(&st);  //获取当前时间 可精确到ms

//设置变化的种子:time为从UNIX的epoch到现在经过的秒数

default_random_engine e((unsigned int)st.wMilliseconds);

 

for (int i = 0; i < 10; ++i){

cout << e()%10 << " ";

}

cout << endl;

 

输出结果:

3 5 7 1 7 5 3 8 5 5

4 8 8 3 8 1 2 7 8 1

0 0 5 0 3 0 7 2 5 0

 

事实是不行,因为限制随机数的范围是一个很复杂并且极其困难的事情(C++11 Primer 第五版中这么说!),因此我们不能指望以上面的方法获得限定范围的随机数,而应该搭配随机分布类来达到我们的目的!

uniform_int_distribution

我们打开uniform_int_distribution的源码:

 

// TEMPLATE CLASS uniform_int_distribution

template<class _Ty = int>

class uniform_int_distribution

: public uniform_int<_Ty>

{ // template class for uniform integer distribution

public:

static_assert(_Is_IntType<_Ty>::value,

"invalid template argument for uniform_int_distribution");

 

通过源码,我们知道了uniform_int_distribution的缺省模参为int,至于_Is_IntType<_Ty>,是我们需要注意的,这是一个检查我们用来实例化uniform_int_distribution模板的模板实参是否符合要求的Trait类!其源码如下:

 

// TEMPLATE STRUCT _Is_IntType

template<class _Ty>

struct _Is_IntType

: _Cat_base<is_same<_Ty, short>::value

|| is_same<_Ty, int>::value

|| is_same<_Ty, long>::value

|| is_same<_Ty, long long>::value

|| _Is_UIntType<_Ty>::value>

{ // determine whether _Ty satisfies <random>'s IntType requirements

};

这个检查对的trait告诉我们,我们用来实例化uniform_int_distribution的模板实参只能是short、int、long、long long(unsigned系列也行),因此如果你使用double来实例化uniform_int_distribution,你会得出这样的错误:

 

假如你这样写了:

uniform_int_distribution<double> u(0,100);

错误:

error C2338: invalid template argument for uniform_int_distribution

 

其实 uniform_int_distribution的官方说明已经很清楚了:

// determine whether _Ty satisfies <random>'s IntType requirements

即这个类模板适用于Integer(整数)!

 

那么如果我们想产生某个范围内的浮点数怎么办呢,那属于我们下面讨论的问题:C++提供了很多随机分布类!

 

因为很多随机分布类只是其功能不同,因此这里给出几种不同功能的随机分布类的使用方法供读者参考就行,读者可自行参考其他随机分布类的功能和使用方法(C++提供了很多方便的不同功能的随机分布类)!

 

@产生某个范围内的随机浮点数

 

SYSTEMTIME st = { 0 };  

GetLocalTime(&st);  //获取当前时间 可精确到ms

//设置变化的种子:time为从UNIX的epoch到现在经过的秒数

default_random_engine e((unsigned int)st.wMilliseconds);

uniform_real_distribution<double> u(0,100);

for (int i = 0; i < 10; ++i){

cout << u(e) << " ";

}

cout << endl;

 

输出结果:

46.5289 51.2135 53.5935 78.6836 17.4968 38.0637 2.42942 42.2676 64.2818 53.2689

3.53658 51.852 21.7963 50.6321 52.7628 2.01251 60.9546 52.2274 64.1844 65.985

59.3397 9.82466 67.9967 80.3652 37.5162 87.5073 9.19025 44.5257 27.0117 28.1994

 

查看其模板的定义:

 

template<class _Ty>

struct _Is_RealType

: _Cat_base<is_same<_Ty, float>::value

|| is_same<_Ty, double>::value

|| is_same<_Ty, long double>::value>

{ // determine whether _Ty satisfies <random>'s RealType requirements

};

可知其支持float、double、long double

 

@生成一个正态分布的随机值序列

#include<iostream>
#include<random>
#include<time.h>
#include<windows.h>
#include<vector>
#include<string>
using namespace std;

void GenerateRandom(){

	SYSTEMTIME st = { 0 };  	
	GetLocalTime(&st);  //获取当前时间 可精确到ms
	//设置变化的种子:time为从UNIX的epoch到现在经过的秒数
	default_random_engine e((unsigned int)st.wMilliseconds);

	normal_distribution<> u(4,1.5);
	vector<unsigned> vals(9);

	for (int i = 0; i < 200; ++i){
		unsigned v = lround(u(e));
		if (v<vals.size())
			++vals[v];
	}

	for (int i = 0; i != vals.size();++i){
		cout << i << ":" << string(vals[i], '*') << endl;
	}
	cout << endl;
}

void main(){
	GenerateRandom();
}

输出结果:

0:

1:*********

2:***************

3:********************************************

4:*********************************************

5:****************************************

6:*******************************

7:*************

8:***

 

@还有例如随机生成bool值bernoulli_distribution类等,读者可自行测试使用!

 

总结:

java中的随机机制和C++中的随机机制策略基本相同,不同在于两者使用的算法不同,前者使用的线性同余算法,后者使用的梅森环式算法,这是他们随机引擎的不同!至于随机分布类,C++将其和引擎进行了分离,因此需要分离使用,而Java中是封装进了Random类中,至于原因,可能和这两种随机机制使用的算法不同有关系!

但是,C++中可不止支持一种引擎,事实上,C++支持多种算法的引擎,我们上面所讨论的只是在特定平台上默认的随机引擎,而且C++支持多种随机分布类,因此C++对于随机数的支持程度比Java更宽泛!

 

参考资料:

1,http://www.cnblogs.com/qcblog/p/8450427.html

2,http://www.cnblogs.com/qcblog/p/8450427.html

3,http://sodino.com/2015/03/20/c-time-micro-nano/

4,《C++11 Primer 第五版

 

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柔弱胜刚强.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值