(点击上方公众号,可快速关注)

如需转载,发送「转载」二字查看说明

本文是为了记录和澄清一个由来已久的关于C语言随机数生成器的误解。

目前所看到的所有公开的关于C随机数生成器的中文资料,都提到经典的线性同余法( LCG, linear congruential generator),并认为是默认的实现方法。这个说法并不准确。以GCC为例,GLIBC的确实现了线性同余法,但是实现的代码块分支在日常使用中不会执行到,线性同余法为C语言默认随机数生成器的说法已过时。

本文将以GLIBC源代码为例,结合掌握的文档,做一回搬运工,总结描述一下GLIBC中随机数生成器的实现。

线性同余法

线性同余法,LCG(linear congruential generator),是经典的伪随机数产生器算法,速度快,容易理解实现。 LCG 算法数学上基于公式:

X(n+1) = (a * X(n) + c) % m

其中,各系数为:模m, 系数a, 0 < a < m,增量c, 0 <= c < m,原始值(种子) 0 <= X(0) < m 。其中参数c, m, a比较敏感,或者说直接影响了伪随机数产生的质量。

GLIBC中对LCG的实现,取a = 1103515245, c = 12345, m = 134217728,即

X(n+1) = (1103515245 * X(n) + 12345) & 2147483647

由于LCG计算简单,极省内存,很适合内存和计算资源比较紧张的嵌入式环境。但LCG有一个严重的缺陷,即产生的伪随机数强依赖于上一次生成的随机数,且重复周期等于随机范围,不能用于随机数要求高的场合。

原因是单状态生成器在每次rand()调用时不会生成完全的伪随机数,实际做的是以伪随机的顺序遍历(0~2^31)范围内的数。意味着当获取到一个为伪随机数时,在当前周期内不会再获取到同一个数,只有在经过2^31次rand()调用之后,才会获取这个数(而且只会获取到这个数)。

线性累加反馈法

线性累加反馈法,即LAFM(linear additive feedback method),以下是GLIBC使用的线性累加反馈法的流程描述。其中,2147483647 = 2^31 – 1,4294967296 = 2^32. 所有变量都是整数。 对于给定的种子常量s, 初始化序列r0…r33通过以下步骤计算:

r(0) = s

r(i) = (16807 * r(i-1) ) % 2147483647 (i = 1…30)

r(i) = r(i-31) (i = 31…33)

注意数乘16807的结果应该由足够大的整数类型存储,避免取模操作之前发生值溢出。r(i-1)在乘法操作已经是32位整数,r(i)计算结果确保是0到2147483646之间的正整数, 即使r(i-1)为负数。

从r34开始的伪随机序列,通过以下的线性反馈循环来计算:

4. r(i) = (r(i-3) + r(i-31)) % 4294967296 (i ≥ 34)

忽略掉r0…r343序列,rand()函数输出的伪随机数o(i)为:

5. o(i) = r(i+344) >> 1

r(i+344)的个位数字移除,生成31位随机数o(i)。

以下为模拟步骤1~4的代码:

r[0]=seed;

for(i=1;i<31;i++){

r[i]=(16807LL*r[i-1])%2147483647;

if(r[i]<0){

r[i]+=2147483647;

}

for(i=31;i<34;i++)r[i]=r[i-31];

for(i=34;i<344;i++)r[i]=r[i-31]+r[i-3];

for(i=344;i

GLIBC 的实现

GLIBC实现了以上两种算法。LAFM生成器标记为 TYPE1, TYPE2, TYPE_3 和 TYPE4 类型,LCG 生成器标记为 TYPE0。相比LCG,LAFM生成器预先生成有很多初始状态,消除了LCG生成器的周期性遍历的属性,在同一个周期内,可以多次获取到相同的随机数。

为了提高随机数生成的时间和空间效率,在计算伪随机序列时GLIBC使用指针指向包含前驱随机值的数组,写法与按上述公式步骤直译的方式有所不同。

int__random_r(buf,result)

structrandom_data *buf;

int32_t *result;

{

int32_t *state;

if(buf==NULL||result==NULL)

gotofail;

state=buf->state;

if(buf->rand_type==TYPE_0)

{

int32_tval=state[0];

val=((state[0]*1103515245)+12345)&0x7fffffff;

state[0]=val;

*result=val;

}

else

{

int32_t *fptr=buf->fptr;

int32_t *rptr=buf->rptr;

int32_t *end_ptr=buf->end_ptr;

int32_tval;

val= *fptr+= *rptr;

/* Chucking least random bit.  */

*result=(val>>1)&0x7fffffff;

++fptr;

if(fptr>=end_ptr)

{

fptr=state;

++rptr;

}

else

{

++rptr;

if(rptr>=end_ptr)

rptr=state;

}

buf->fptr=fptr;

buf->rptr=rptr;

}

return0;

fail:

__set_errno(EINVAL);

return-1;

}

具体使用哪个生成器依赖于初始状态集合,由initstate()函数生成:

int__initstate_r(seed,arg_state,n,buf)

unsignedintseed;

char*arg_state;

size_tn;

structrandom_data *buf;

{

if(buf==NULL)

gotofail;

int32_t *old_state=buf->state;

if(old_state!=NULL)

{

intold_type=buf->rand_type;

if(old_type==TYPE_0)

old_state[-1]=TYPE_0;

else

old_state[-1]=(MAX_TYPES *(buf->rptr-old_state))+old_type;

}

inttype;

if(n>=BREAK_3)

type=n

elseif(n

{

if(n

gotofail;

type=TYPE_0;

}

else

type=n

intdegree=random_poly_info.degrees[type];

intseparation=random_poly_info.seps[type];

buf->rand_type=type;

buf->rand_sep=separation;

buf->rand_deg=degree;

int32_t *state= &((int32_t *)arg_state)[1];/* First location.  */

/* Must set END_PTR before srandom.  */

buf->end_ptr= &state[degree];

buf->state=state;

__srandom_r(seed,buf);

state[-1]=TYPE_0;

if(type!=TYPE_0)

state[-1]=(buf->rptr-state)*MAX_TYPES+type;

return0;

fail:

__set_errno(EINVAL);

return-1;

}

总结

LCG生成器在状态数组(buf->state)长度为8字节时才会使用。 状态数组长度更大时则会启用LAFM生成器。通常在使用rand()方法时,会使用srand()设置种子常量,这时状态数组默认就是128字节, 所以实际会启用LAFM生成器。

最后,以上分析如果有偏差,很可能是作者对资料或代码的理解问题,欢迎即时反馈。

参考资料:

纯线性同余随机数生成器介绍

http://www.cnblogs.com/xkfz007/archive/2012/03/27/2420154.html

random/initstate源代码

https://github.com/lattera/glibc/blob/master/stdlib/random_r.c

glibc rand function implementation

http://stackoverflow.com/questions/18634079/glibc-rand-function-implementation

The GLIBC random number generator

http://www.mscs.dal.ca/~selinger/random/

觉得本文有帮助?请分享给更多人

关注「CPP开发者」

看更多精选C/C++技术文章

↓↓↓

生成随机数c 语言,C 语言随机数生成器的实现分析相关推荐

  1. C语言中的随机数生成器

    在我们编写程序的时候,经常会需要电脑给我们随机生成一个整数,这个时候我们就需要一个随机数的生成器--rand().rand()为C语言中的函数,调用该函数需要加头文件#include<stdli ...

  2. C语言rand函数生成随机数详解和示例

    文章目录 1.生成随机数 2.生成一定范围随机数 3.获取视频教程 4.版权声明 在C/C++程序开发中,会经常用到随机数这个功能,例如编写游戏类(纸牌)的程序时就需要用到随机数. 1.生成随机数 在 ...

  3. c语言中有关随机数的程序,C语言中随机数相关问题

    用C语言产生随机数重要用到rand函数.srand函数.及宏RAND_MAX(32767),它们均在stdlib.h中进行了声明. int rand(void);//生成一个随机数 voidsrand ...

  4. c语言输出字母随机数,你好,怎样用c语言输出一个1到100的随机数

    你好,怎样用c语言输出一个1到100的随机数以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧! 你好,怎样用c语言输出一个1 ...

  5. *C语言如何使用随机数?

    C语言如何使用随机数? 功能介绍 推送一些C语言方面的知识,提供C语言/C++语言资料,讨论和学习C语言/C++编程知识.给学习C语言的同学们一些帮助. 随机数的使用,是不少在学C语言过程中进行一些小 ...

  6. c语言随机数毫秒变化,C语言随机数生成

    C语言/C++产生随机数要用到的是rand()函数,srand()函数,C语言/C++里没有自带的random(int number)函数. 先从程序上去认识吧. #include #include ...

  7. C语言 rand函数生成随机数

    在实际的项目中,有时候需要生成一个随机数,在C语言中随机数的生成可以通过使用rand函数来实现. rand函数包含在头文件stdlib.h里,因此使用rand函数需要声明包含stdlib.h. #in ...

  8. C语言随机数rand用法,【转载】随机数的产生 c语言rand的用法

    式子如下 : rand = rand*const_1 + c_var; srand函数就是给它的第一个rand值. 用"int x = rand() % 100;"来生成 0 到 ...

  9. linux sql生成随机数,Linux的两种随机数生成器

    Linux下有两个特殊设备文件/dev/random和/de/urandom,用于生成随机数./dev/random生成的随机数与当前使用的计算机硬件状态相关,提高了安全性,非常适合对随机数质量要求很 ...

  10. c语言rand函数生成随机数,详解C语言生成随机数rand函数的用法

    说到rand函数,大家是不是会和EXCEL中的rand函数混淆,当小编第一次接触的时候也以为是EXCEL的函数,本文是爱站技术频道小编为大家带来的详解C语言生成随机数rand函数的用法,一起来看看吧! ...

最新文章

  1. Tech.Ed2005 讲义下载地址
  2. ISME|宏转录组揭示参与深海碳氮循环的微生物
  3. MAX3232EUE小知识
  4. 中国大学MOOC 计算机组成原理第5章 测试(上)
  5. Office基础和计算机操作基础的知识点(一)
  6. 玩转oracle 11g(48):oracle命令窗口执行sql语句
  7. Security+ 学习笔记20 身份证明
  8. equals方法的使用几种情况
  9. java类型的对象可以存储属性_重识JVM(一)-类与对象在JVM中是如何存储的
  10. java游戏下载怎么玩_jar的手机游戏怎么玩?java手机游戏的玩法
  11. HTML 拓扑 http://www.hightopo.com/demos/index.html 拓扑
  12. Android键盘 AOSP监听delete按键
  13. 比尔·盖茨表示 AI应被用来改善教育医疗
  14. 第二十五期 总结《路由器就是开发板》
  15. C语言电话簿程序设计,2010电话簿管理程序-c语言程序设计-毕业论文.doc
  16. 皮尔逊(Pearson)相关系数与spearman相关系数(Python实现)
  17. 清华大学计算机系2015分数线,2015年清华大学录取分数线
  18. ERTEC200P-2 PROFINET设备完全开发手册(9-2)
  19. 2019京东618活动提报要求一览
  20. 雷军:做互联网需7字诀

热门文章

  1. 2 申请GAE的AppId与Google SVN代码托管
  2. 法拉利与区块链公司Velas合作,进军区块链行业
  3. 一款简单强大的下载软件(freedownloadmanager)
  4. 我的世界服务器精英怪修改,我的世界稀有精英怪
  5. 万博智云新一代云原生备份容灾平台发布会成功举办,让企业备份容灾成为常态
  6. java工具类——字符串拼接,逗号分隔
  7. postman如何导入API.json文件
  8. 陪玩网站源码开发,如何设计一个高可用的订单系统
  9. Flink / Scala 实战 - 6.使用 Jedis、JedisPool 作为 Source 读取数据
  10. JAVAEE---HTTP协议+HTTPS