你真的懂随机数?

Author : Jasper Yang
School : Bupt

Q:为什么要写这篇文章?
A:因为我发现在最近的科学计算中,常常遇到随机数,所有的随机数都是基于0,1随机,而这个0,1随机怎么实现呢?下面我会娓娓道来~

这篇文章不同于网路上的杂散的技术文,我是针对 random 这么一个论题展开调研最后将所有相关的知识进行整理叙述,希望每个人看完都可以得到小小的提升~

& 什么是随机数

随机数:数学上产生的都是伪随机数,真正的随机数使用物理方法产生的

随机数种子:随机数的产生是由算术规则产生的,在c++中,srand(seed)的随机数种子不同,rand()的随机数值就不同,倘若每次的随机数种子一样,则rand()的值就一样。所以要产生随机数,则srand(seed)的随机数种子必须也要随机的。在 python 中就是 random.seed()来设置种子。

下面我讲的随机数不仅仅讲随机数生成的原理,也会讲在python中以及在c++中怎么去实现,当然,大部分资料也都是网上找的,我只是做了一个整理汇总,并用自己的语言加以叙述。

& 随机数的原理

这里我看了一篇博客,由于这篇博客是那个博主转的,但是该博主并没有表明是从哪里转来的,我就不po出链接了,大家往下看~

有位朋友问那博主关于一段程序的错误。

C/C++ code
for (int i =0;i< n;++i)
{srand((unsigned)time( NULL )); int r = rand()%100;cout << r << ",";
}

这里很明显他是想输出一串小于100的随机的数列.可是运行结果输出的却是类似 97,97,97,97,....97,30,30,30,30,30,30,30,30,30,30,30,30,....,27,27,27,27,27,27,....的序列.很明显这样完全看不出有任何的随机性.这是由于他对C的rand函数不理解导致的错误用法.而这两天逛C#区我也同样看到了几个类似的错误用法(C和C#的rand从大体的原理上差不多).想想自己初学的时候类似的错误犯得也不少.所以自己下去查了写资料总结了在随机数使用上的一些错误的用法.希望能对初学者有所帮助。

现在各种语言中的随机数产生函数所产生的"随机数",实际上被称之为"伪随机数".可以将
整个随机数函数看做这样一个表达式:

$$A = R(s)$$

其中R是随机函数,s是种子.A是一个数列.即对于任意一个种子s,经过R的计算后,总有一个确定的数列A与之对应.而当在C#里调用var rnd = new Random (s)或在C里调用srand(s)实质上所做工作之一就是设定这个种子.而rnd.Next();或rand()只不过是在A上取下一个元素而已.当然实际的实现不可能事先计算一个数列A,所以rand()相当于由s计算出下一个数字s',然后将s'作为新的种子赋值给s,最后将s'作为结果返回。

往细了讲,就是这样。

如果约定:$a_1=f(seed),a_{n+1}=f(an)$
那你可以行到一个序列:$a_1,a_2,a_3...a_n$,那么要制作一个伪随机函数rand,只需要让它每调用一次就返回序列的下一个元素就行。

下面是两种常见的错误做法

C# code
for (int i=0;i<n;++i)
{var rnd = new Random (s);//s是实先确定的一个数字Console.Write ("{0},",rnd.Next());
}

这样,每次使用Random,都去申请了一个变量rnd,然后才用这个变量去找随机数(rnd.Next())。这样其实就是在随机数的序列中总是在找第一个。这样下来,第一个数肯定是固定的,就不存在什么随机数了。

第二种情况更加常见。

C# code
for (int i=0;i<n;++i)
{var rnd = new Random ();//用系统时间作为种子Console.Write ("{0},",rnd.Next());
}

之前的第一种情况使用了一个固定的常数s来做种子,这里选用了系统时间做种子,想要达到随机的效果,但是得到的结果往往就会是和博主那位朋友一样的结果97,97,97,97,....97,30,30,30,30,30,30,30,30,30,30,30,30,....,27,27,27,27,27,27,.... 。

这是因为Windows系统时钟的更新频率大概在10ms左右.而这个for循环的执行显然要快
得多.于是在一段执行时间内Environment.TickCount (Random的默认种子)或是C的time函数返回的都是同一个值.从而导致rnd.Next在一段时间内返回一个常数。

所以正确的做法应该是把种子移出循环之外。

C# code
var rnd = new Random ();//用系统时间作为种子
for (int i=0;i<n;++i)
{Console.Write ("{0},",rnd.Next());
}

各种库中是怎么实现随机数呢?
在 Linux 下实现的方式类似如下

static unsigned long next = 1;/* RAND_MAX assumed to be 32767 */
int myrand(void) {next = next * 1103515245 + 12345;return((unsigned)(next/65536) % 32768);
}void mysrand(unsigned seed) {next = seed;
}

myrand、mysrand分别对应rand和srand,但实际的rand实现会复杂一些。

下面是这位博主实现的方式,其实挺简单的,我们每个人都可以实现一种自己想要的随机数方式加到自己的私有库中~

** Copyright (c) 2008 Microsoft::Tsorgy.Utils, Reserved.* * Filename:    @(#)Random.cs* Create by:   TsOrgY* Email:       tsorgy@gmail.com* Date:        2008/12/27 15:01:40* * Classname:   Random* Description: 一种能够产生满足某些随机性统计要求的数字序列的设备.*              */
using System;
using System.Runtime.InteropServices;
namespace Tsorgy.Utils {/// <summary>/// 表示伪随机数生成器,一种能够产生满足某些随机性统计要求的数字序列的设备./// </summary>[Serializable][ComVisible(true)]public class Random {private int inext;private int inextp;private const int MBIG = 0x7fffffff;private const int MSEED = 0x9a4ec86;private const int MZ = 0;private int[] SeedArray;/// <summary>/// 使用与时间相关的默认种子值,初始化 Random 类的新实例./// </summary>public Random(): this(Environment.TickCount) {}/// <summary>/// 使用指定的种子值初始化 System.Random 类的新实例./// </summary>/// <param name="Seed">用来计算伪随机数序列起始值的数字。如果指定的是负数,则使用其绝对值。</param>/// <exception cref="System.OverflowException">Seed 为 System.Int32.MinValue,在计算其绝对值时会导致溢出。</exception>public Random(int Seed) {this.SeedArray = new int[0x38];int num2 = 0x9a4ec86 - Math.Abs(Seed);this.SeedArray[0x37] = num2;int num3 = 1;for (int i = 1; i < 0x37; i++) {int index = (0x15 * i) % 0x37;this.SeedArray[index] = num3;num3 = num2 - num3;if (num3 < 0) {num3 += 0x7fffffff;}num2 = this.SeedArray[index];}for (int j = 1; j < 5; j++) {for (int k = 1; k < 0x38; k++) {this.SeedArray[k] -= this.SeedArray[1 + ((k + 30) % 0x37)];if (this.SeedArray[k] < 0) {this.SeedArray[k] += 0x7fffffff;}}}this.inext = 0;this.inextp = 0x15;Seed = 1;}private double GetSampleForLargeRange() {int num = this.InternalSample();if ((((this.InternalSample() % 2) == 0) ? 1 : 0) != 0) {num = -num;}double num2 = num;num2 += 2147483646.0;return (num2 / 4294967293);}private int InternalSample() {int inext = this.inext;int inextp = this.inextp;if (++inext >= 0x38) {inext = 1;}if (++inextp >= 0x38) {inextp = 1;}int num = this.SeedArray[inext] - this.SeedArray[inextp];if (num < 0) {num += 0x7fffffff;}this.SeedArray[inext] = num;this.inext = inext;this.inextp = inextp;return num;}/// <summary>/// 返回非负随机数./// </summary>/// <returns>大于或等于零且小于 System.Int32.MaxValue 的 32 位带符号整数。</returns>public virtual int Next() {return this.InternalSample();}/// <summary>/// 返回一个小于所指定最大值的非负随机数./// </summary>/// <param name="maxValue">要生成的随机数的上界(随机数不能取该上界值)。maxValue 必须大于或等于零。</param>/// <returns>大于或等于零且小于 maxValue 的 32 位带符号整数,即:返回的值范围包括零但不包括 maxValue。</returns>/// <exception cref="System.ArgumentOutOfRangeException">maxValue 小于零。</exception>public virtual int Next(int maxValue) {if (maxValue < 0) {throw new ArgumentOutOfRangeException("maxValue", string.Format("'{0}' must be greater than zero.", maxValue));}return (int) (this.Sample() * maxValue);}/// <summary>/// 返回一个指定范围内的随机数./// </summary>/// <param name="minValue">返回的随机数的下界(随机数可取该下界值)。</param>/// <param name="maxValue">返回的随机数的上界(随机数不能取该上界值)。maxValue 必须大于或等于 minValue。</param>/// <returns>一个大于或等于 minValue 且小于 maxValue 的 32 位带符号整数,即:返回的值范围包括 minValue 但不包括 maxValue。如果minValue 等于 maxValue,则返回 minValue。</returns>/// <exception cref="System.ArgumentOutOfRangeException">minValue 大于 maxValue。</exception>public virtual int Next(int minValue, int maxValue) {if (minValue > maxValue) {throw new ArgumentOutOfRangeException("minValue", string.Format("'{0}' cannot be greater than {1}.", minValue, maxValue));}long num = maxValue - minValue;if (num <= 0x7fffffffL) {return (((int) (this.Sample() * num)) + minValue);}return (((int) ((long) (this.GetSampleForLargeRange() * num))) + minValue);}/// <summary>/// 用随机数填充指定字节数组的元素./// </summary>/// <param name="buffer">包含随机数的字节数组。</param>/// <exception cref="System.ArgumentNullException">buffer 为 null。</exception>public virtual void NextBytes(byte[] buffer) {if (buffer == null) {throw new ArgumentNullException("buffer");}for (int i = 0; i < buffer.Length; i++) {buffer[i] = (byte) (this.InternalSample() % 0x100);}}/// <summary>/// 返回一个介于 0.0 和 1.0 之间的随机数./// </summary>/// <returns>大于或等于 0.0 而小于 1.0 的双精度浮点数字。</returns>public virtual double NextDouble() {return this.Sample();}/// <summary>/// 返回一个介于 0.0 和 1.0 之间的随机数./// </summary>/// <returns>大于或等于 0.0 而小于 1.0 的双精度浮点数字。</returns>protected virtual double Sample() {return (this.InternalSample() * 4.6566128752457969E-10);}}
}

这里我要另外提到一个大家听到了很多次的东西 ------------> 线性同余法

这也是实现随机数的一种方式

线性同余方法(LCG)

它的递归公式:

$$N_{j+1} = (A * N_j +B) (mod M)$$

其中A,B,M是产生器设定的常数。

LCG的周期最大为M,但大部分情况都会少于M。要令LCG达到最大周期,应符合以下条件:

  1. B,M互质

  2. M的所有质因子的积能整除A-1

  3. 若M是4的倍数,A-1也是

  4. A,B,$N_0$都比M小

  5. A,B是正整数

最后生成的就是一个 <$N_i$> 序列,这个序列应该满足下面的几个条件。

  1. 这个函数应该是一个完整周期的产生函数。也就是说,这个函数应该在重复之前产生出0 到m之间的所有数

  2. 产生的序列应该看起来是随机的

  3. 这个函数应该用32bit 算术高效实现

实现

#include <stdio.h>
#include <time.h>
static unsigned long rand_seed;
void mysrand (unsigned long int);
void myrand ();
int
main (void)
{  int i;  mysrand (time (NULL));  for (i = 0; i < 100; i++)  {  myrand ();  }  return 0;
}  void
mysrand (unsigned long seed)
{  rand_seed = seed;
}  void
myrand ()
{  rand_seed = (rand_seed * 16807L) % ((1 << 31) - 1);  printf ("%ld ", rand_seed);
}  

可以看到,这个实现和上面提到的 linux 的实现很像,其实就是一样的。

& 随机数使用

因为最近用的c++和python特别的多(我觉得这两个语言是程序员们最需要掌握的两种语言,别的都是补充 ~:)),所以下面我就只讲这两种语言的实现方式。

c++

实例程序

#include "stdafx.h"
#include <time.h>
#include <stdlib.h>
int _tmain(int argc, _TCHAR* argv[])
{// 初始化随机数种子// time函数返回从1970年1月1日零时零分零秒到目前为止所经过的时间,单位为秒srand((int)time(NULL));int j;for (int i = 0; i < 10; i++) {j = (rand() * 10) / RAND_MAX + 1; // 生成1~10之间的随机数printf("j = %d \n", j);}unsigned start = (rand() * 1000)/ RAND_MAX + 15550; // 生成15550~16549之间的随机数printf("start = %d \n", start);start &= ~1; // 把start变为偶数,如果是奇数,则start变为start - 1的偶数printf("start = %d \n", start);getchar();return 0;
}

c++ 其实就是 srand 和 rand 两个函数。
上面的都只是生成的整数,如果需要浮点数什么的就需要自己再加以处理,而在python中提供了比较多的函数。

python

这块的内容是 Capricorn的实验室的整理。其实这块内容直接去官网的doc翻译就可以了,但是我有点懒,不太想去看了,就用了这篇博文的内容~

<h3>random.random</h3>
random.random()用于生成一个0到1的随机符点数: 0 <= n < 1.0

<h3>random.uniform</h3>

random.uniform的函数原型为:random.uniform(a, b),用于生成一个指定范围内的随机符点数,两个参数其中一个是上限,一个是下限。如果a>b,则生成的随机数n: a <= n <= b。如果 $a<b$, 则 b <= n <= a。

<h3>random.randint</h3>

random.randint()的函数原型为:random.randint(a, b),用于生成一个指定范围内的整数。其中参数a是下限,参数b是上限,生成的随机数n: a <= n <= b。

<h3>random.randrange</h3>

random.randrange 的函数原型为:random.randrange([start], stop[, step]),从指定范围内,按指定基数递增的集合中 获取一个随机数。如:random.randrange(10, 100, 2),结果相当于从[10, 12, 14, 16, ... 96, 98]序列中获取一个随机数。random.randrange(10, 100, 2)在结果上与 random.choice(range(10, 100, 2) 等效。

<h3>random.choice</h3>

random.choice从序列中获取一个随机元素。其函数原型为:random.choice(sequence)。参数sequence表示一个有序类型。这里要说明 一下:sequence在python不是一种特定的类型,而是泛指一系列的类型。list, tuple, 字符串都属于sequence。有关sequence可以查看python手册数据模型这一章。下面是使用choice的一些例子:

print random.choice("学习Python")
print random.choice(["JGood", "is", "a", "handsome", "boy"])
print random.choice(("Tuple", "List", "Dict"))  

<h3>random.shuffle</h3>

random.shuffle的函数原型为:random.shuffle(x[, random]),用于将一个列表中的元素打乱

<h3>random.sample</h3>

random.sample的函数原型为:random.sample(sequence, k),从指定序列中随机获取指定长度的片断。sample函数不会修改原有序列。

OK,告一段落了~,朋友们,有没有觉得进步了一点点呢~

paper done 2017/05/13

python学习笔记 --- 随机数进阶相关推荐

  1. Python学习笔记(1)---B站黑马程序员

    Python学习笔记(1)-B站黑马程序员 Python学习笔记(2)-B站黑马程序员 Python学习笔记(3)-B站黑马程序员 文章目录 Linux基础 Python基础 一.Python介绍 0 ...

  2. python学习笔记目录

    人生苦短,我学python学习笔记目录: week1 python入门week2 python基础week3 python进阶week4 python模块week5 python高阶week6 数据结 ...

  3. OpenCV之Python学习笔记(1)(2): 图像的载入、显示和保存 图像元素的访问、通道分离与合并

    OpenCV之Python学习笔记 一直都在用Python+OpenCV做一些算法的原型.本来想留下发布一些文章的,可是整理一下就有点无奈了,都是写零散不成系统的小片段.现在看到一本国外的新书< ...

  4. python 学习笔记 (核心)

    python    学习笔记 (核心) Python解释器从头到尾一行接一行执行脚本 # -*- coding: UTF-8 -*-    //字符编码 不区分单引号和双引号,x='hello',x[ ...

  5. OpenCV之Python学习笔记

    RSS订阅 登陆 注册 原文链接地址:http://www.itozi.net/19477.html OpenCV之Python学习笔记 ITOZI 发布于 2015-08-06 分类:OpenSta ...

  6. 【Python学习笔记—保姆版】第四章—关于Pandas、数据准备、数据处理、数据分析、数据可视化

    第四章 欢迎访问我搞事情的[知乎账号]:Coffee 以及我的[B站漫威剪辑账号]:VideosMan 若我的笔记对你有帮助,请用小小的手指,点一个大大的赞哦. #编译器使用的是sypder,其中&q ...

  7. python学习笔记_week22

    python学习笔记_week22 note 知识点概要- Session- CSRF- Model操作- Form验证(ModelForm)- 中间件- 缓存- 信号 内容详细: 1. Sessio ...

  8. 【Python学习笔记(一)—— 初识Python】

    Python学习笔记(一) 文章目录 Python学习笔记(一) 前言 一.Python简介 二.初识Python 1.最简单的Python程序 2.数据类型和变量 3.流程控制 4.函数 5.类 6 ...

  9. Python 学习笔记——入门

    文章目录 〇.Python 是什么 一.推荐的教程 二.这篇学习笔记适合什么人 三.环境 1. 操作系统 对于 Windows 对于 Ubuntu 对于其他操作系统 2. Python 对于 Wind ...

最新文章

  1. Linux使用expect自动登录,linux中使用expect实现自动登录(示例代码)
  2. django 快速实现session的操作
  3. magent实现memcached集群的一个问题
  4. 当深度学习遇上量化交易——公开信息篇
  5. 福州大学计算机学院董晨老师,福州大学代表队高分斩获第三届福建省高校网络空间安全大赛冠军...
  6. 使用 jQuery Mobile 与 HTML5 开发 Web App (一) ——开发原则
  7. (92)低速接口UART、IIC、SPI介绍,面试必问(十六)(第19天)
  8. 日常(关于游泳之列的真理问题讨论)
  9. IOS PhoneGap项目调用NATIVE
  10. EGE基础:鼠标消息篇
  11. 淘宝分布式配置管理服务Diamond
  12. 修改ubuntu键盘布局
  13. mysql update convert_Oracle/云MySQL/MsSQL“大迁移”真相及最优方案
  14. 【Flink基础】-- 高效学习 flink kubernetes operator 的一些建议
  15. Android#studio@快捷键
  16. 注册登录会员抽奖系统
  17. 盲盒是怎么赚钱的?(盲盒App的盈利逻辑)
  18. nest keyword_Alexa仍将与Nest合作(这是一个问题)
  19. 【php】PHP单例模式
  20. Spring项目使用mkcert制作自签名证书

热门文章

  1. 面试官问:JS的继承
  2. Flutter RichText支持自定义文字背景
  3. Docker镜像构成和定制
  4. linux中/usr下文件权限修改setuid导致的问题
  5. cxGrid 在 GridMode = True 模式下实现标题点击排序以及标题列过滤筛选!!!
  6. 深入理解javascript原型和闭包
  7. 【我们都爱Paul Hegarty】斯坦福IOS8公开课个人笔记24 popovers弹窗
  8. OpenJudge/Poj 1226 Substrings
  9. 【IfICan】脚步很乱!
  10. pointcut注解_Spring AOP使用指南,详细了解AOP相关注解