Random的实现原理

昨天一朋友问了下我关于他写的一段程序的错误.
其中有一个问题就是关于随机数的.他的代码如下(C++代码).
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从大体
的原理上差不多).想想自己初学的时候类似的错误犯得也不少.所以自己下去查了写资料
总结了在随机数使用上的一些错误的用法.希望能对初学者有所帮助.

先来说说随机数算法的实现.借用C数值算法里的一句话:利用计算机,这种人类所设计的
各种机器中最精确,最能做出确切判断的机器,来产生"随机数",这看上去有些自相矛盾.甚至在
概念上是讲不通的.任何程序必将产生完全可以预计的结果.因而不是真正的"随机数".

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

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'作为结果返回.

接下来就是两种常见的错误用法了:
C# code
   
   
for ( int i = 0 ;i < n; ++ i) { var rnd = new Random (s); // s是实先确定的一个数字 Console.Write ( " {0}, " ,rnd.Next()); }

这样使用随机数产生器只会产生一个固定的常数N.
因为每次都用同一个种子初始化了随机数产生器后调用了Next().
所取得的都是数列A上的第一个元素.而这个元素的值肯定是固定
的(当然N取什么值要看随机函数的实现而定).

而第二种情况就更常见了:
C# code
   
   
for ( int i = 0 ;i < n; ++ i) { var rnd = new Random (); // 用系统时间作为种子 Console.Write ( " {0}, " ,rnd.Next()); }


这样调用应该是希望通过时间的不同来达到随机的效果;但是得到的结果就和我那位朋友一样.会是形似
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()); }



就我个人习惯来说.在一个经常用到随机数的程序中,我会在Main中就初始化一个全局的随机数产生器,在
之后要用到随机数的地方就直接调用Next,而不用每次都构造一个Random.

--------------

  1. *
  2.  * Copyright (c) 2008 Microsoft::Tsorgy.Utils, Reserved.
  3.  * 
  4.  * Filename:    @(#)Random.cs
  5.  * Create by:   TsOrgY
  6.  * Email:       tsorgy@gmail.com
  7.  * Date:        2008/12/27 15:01:40
  8.  * 
  9.  * Classname:   Random
  10.  * Description: 一种能够产生满足某些随机性统计要求的数字序列的设备.
  11.  *              
  12.  */
  13. using System;
  14. using System.Runtime.InteropServices;
  15. namespace Tsorgy.Utils {
  16.     /// <summary>
  17.     /// 表示伪随机数生成器,一种能够产生满足某些随机性统计要求的数字序列的设备.
  18.     /// </summary>
  19.     [Serializable]
  20.     [ComVisible(true)]
  21.     public class Random {
  22.         private int inext;
  23.         private int inextp;
  24.         private const int MBIG = 0x7fffffff;
  25.         private const int MSEED = 0x9a4ec86;
  26.         private const int MZ = 0;
  27.         private int[] SeedArray;
  28.         /// <summary>
  29.         /// 使用与时间相关的默认种子值,初始化 Random 类的新实例.
  30.         /// </summary>
  31.         public Random()
  32.             : this(Environment.TickCount) {
  33.         }
  34.         /// <summary>
  35.         /// 使用指定的种子值初始化 System.Random 类的新实例.
  36.         /// </summary>
  37.         /// <param name="Seed">用来计算伪随机数序列起始值的数字。如果指定的是负数,则使用其绝对值。</param>
  38.         /// <exception cref="System.OverflowException">Seed 为 System.Int32.MinValue,在计算其绝对值时会导致溢出。</exception>
  39.         public Random(int Seed) {
  40.             this.SeedArray = new int[0x38];
  41.             int num2 = 0x9a4ec86 - Math.Abs(Seed);
  42.             this.SeedArray[0x37] = num2;
  43.             int num3 = 1;
  44.             for (int i = 1; i < 0x37; i++) {
  45.                 int index = (0x15 * i) % 0x37;
  46.                 this.SeedArray[index] = num3;
  47.                 num3 = num2 - num3;
  48.                 if (num3 < 0) {
  49.                     num3 += 0x7fffffff;
  50.                 }
  51.                 num2 = this.SeedArray[index];
  52.             }
  53.             for (int j = 1; j < 5; j++) {
  54.                 for (int k = 1; k < 0x38; k++) {
  55.                     this.SeedArray[k] -= this.SeedArray[1 + ((k + 30) % 0x37)];
  56.                     if (this.SeedArray[k] < 0) {
  57.                         this.SeedArray[k] += 0x7fffffff;
  58.                     }
  59.                 }
  60.             }
  61.             this.inext = 0;
  62.             this.inextp = 0x15;
  63.             Seed = 1;
  64.         }
  65.         private double GetSampleForLargeRange() {
  66.             int num = this.InternalSample();
  67.             if ((((this.InternalSample() % 2) == 0) ? 1 : 0) != 0) {
  68.                 num = -num;
  69.             }
  70.             double num2 = num;
  71.             num2 += 2147483646.0;
  72.             return (num2 / 4294967293);
  73.         }
  74.         private int InternalSample() {
  75.             int inext = this.inext;
  76.             int inextp = this.inextp;
  77.             if (++inext >= 0x38) {
  78.                 inext = 1;
  79.             }
  80.             if (++inextp >= 0x38) {
  81.                 inextp = 1;
  82.             }
  83.             int num = this.SeedArray[inext] - this.SeedArray[inextp];
  84.             if (num < 0) {
  85.                 num += 0x7fffffff;
  86.             }
  87.             this.SeedArray[inext] = num;
  88.             this.inext = inext;
  89.             this.inextp = inextp;
  90.             return num;
  91.         }
  92.         /// <summary>
  93.         /// 返回非负随机数.
  94.         /// </summary>
  95.         /// <returns>大于或等于零且小于 System.Int32.MaxValue 的 32 位带符号整数。</returns>
  96.         public virtual int Next() {
  97.             return this.InternalSample();
  98.         }
  99.         /// <summary>
  100.         /// 返回一个小于所指定最大值的非负随机数.
  101.         /// </summary>
  102.         /// <param name="maxValue">要生成的随机数的上界(随机数不能取该上界值)。maxValue 必须大于或等于零。</param>
  103.         /// <returns>大于或等于零且小于 maxValue 的 32 位带符号整数,即:返回的值范围包括零但不包括 maxValue。</returns>
  104.         /// <exception cref="System.ArgumentOutOfRangeException">maxValue 小于零。</exception>
  105.         public virtual int Next(int maxValue) {
  106.             if (maxValue < 0) {
  107.                 throw new ArgumentOutOfRangeException("maxValue"string.Format("'{0}' must be greater than zero.", maxValue));
  108.             }
  109.             return (int) (this.Sample() * maxValue);
  110.         }
  111.         /// <summary>
  112.         /// 返回一个指定范围内的随机数.
  113.         /// </summary>
  114.         /// <param name="minValue">返回的随机数的下界(随机数可取该下界值)。</param>
  115.         /// <param name="maxValue">返回的随机数的上界(随机数不能取该上界值)。maxValue 必须大于或等于 minValue。</param>
  116.         /// <returns>一个大于或等于 minValue 且小于 maxValue 的 32 位带符号整数,即:返回的值范围包括 minValue 但不包括 maxValue。如果minValue 等于 maxValue,则返回 minValue。</returns>
  117.         /// <exception cref="System.ArgumentOutOfRangeException">minValue 大于 maxValue。</exception>
  118.         public virtual int Next(int minValue, int maxValue) {
  119.             if (minValue > maxValue) {
  120.                 throw new ArgumentOutOfRangeException("minValue"string.Format("'{0}' cannot be greater than {1}.", minValue, maxValue));
  121.             }
  122.             long num = maxValue - minValue;
  123.             if (num <= 0x7fffffffL) {
  124.                 return (((int) (this.Sample() * num)) + minValue);
  125.             }
  126.             return (((int) ((long) (this.GetSampleForLargeRange() * num))) + minValue);
  127.         }
  128.         /// <summary>
  129.         /// 用随机数填充指定字节数组的元素.
  130.         /// </summary>
  131.         /// <param name="buffer">包含随机数的字节数组。</param>
  132.         /// <exception cref="System.ArgumentNullException">buffer 为 null。</exception>
  133.         public virtual void NextBytes(byte[] buffer) {
  134.             if (buffer == null) {
  135.                 throw new ArgumentNullException("buffer");
  136.             }
  137.             for (int i = 0; i < buffer.Length; i++) {
  138.                 buffer[i] = (byte) (this.InternalSample() % 0x100);
  139.             }
  140.         }
  141.         /// <summary>
  142.         /// 返回一个介于 0.0 和 1.0 之间的随机数.
  143.         /// </summary>
  144.         /// <returns>大于或等于 0.0 而小于 1.0 的双精度浮点数字。</returns>
  145.         public virtual double NextDouble() {
  146.             return this.Sample();
  147.         }
  148.         /// <summary>
  149.         /// 返回一个介于 0.0 和 1.0 之间的随机数.
  150.         /// </summary>
  151.         /// <returns>大于或等于 0.0 而小于 1.0 的双精度浮点数字。</returns>
  152.         protected virtual double Sample() {
  153.             return (this.InternalSample() * 4.6566128752457969E-10);
  154.         }
  155.     }
  156. }
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值