from:https://www.xcode.me/more/net-csharp-generate-random

随机数生成方法可以说是任何编程语言必备的功能,它的重要性不言而言,在C#中我们通常使用Random类生成随机数,在一些场景下,我却发现Random生成的随机数并不可靠,在下面的例子中我们通过循环随机生成5个随机数:

for (int i = 0; i < 5; i++) {     Random random = new Random();     Console.WriteLine(random.Next()); }

这段代码执行后的结果如下所示:

2140400647 2140400647 2140400647 2140400647 2140400647

通过以上结果可知,随机数类生成了5个相同的数,这并非我们的预期,为什么呢?为了弄清楚这个问题,零度剖析了微软官方的开源Random类,发现在C#中生成随机数使用的算法是线性同余法,经百科而知,这种算法生成的不是绝对随机,而是一种伪随机数,线性同余法算法的的公式是:

第N+1个数 = ( 第N个数 * A + B) % M

上面的公式中A、B和M分别为常数,是生成随机数的因子,如果之前从未通过同一个Random对象生成过随机数(也就是调用过Next方法),那么第N个随机数为将被指定为一个默认的常数,这个常数在创建一个Random类时被默认值指定,Random也提供一个构造函数允许开发者使用自己的随机数因子,这一切可通过微软官方开源代码看到:

public Random() : this(Environment.TickCount) { }  public Random(int Seed) { }

通过默认构造函数创建Random类时,一个Environment.TickCount对象作为因子被默认传递给第二个构造函数,Environment.TickCount表示操作系统启动后经过的毫秒数,计算机的运算运算速度远比毫秒要快得多,这导致一个的具有毫秒精度的因子参与随机数的生成过程,但在5次循环中,我们使用了同一个毫秒级的因子,从而生成相同的随机数,另外,第N+1个数的生成与第N个数有着直接的关系。

在上面的例子中,假设系统启动以来的毫秒数为888毫秒,执行5次循环用时只有0.1毫秒,这导致在循环中创建的5个Random对象都使用了相同的888因子,每次被创建的随机对象又使用了相同的第N个数(默认为常数),通过这样的假设我们不难看出,上面的结果是必然的。

现在我们改变这个格局,在循环之外创建一个Random对象,在每次循环中引用它,并通过它生成随机数,并在同一个对象上多次调用Next方法,从而不断变化第N个数,代码如下所示:

Random random = new Random();  for (int i = 0; i < 5; i++) {     Console.WriteLine(random.Next()); }

执行后的结果如下所示:

391098894 1791722821 1488616582 1970032058 201874423

我们看到这个结果确实证实了我们上面的推断,第1次循环时公式中的第N个数为默认常数;当第二次循环时,第N个数为391098894,随后不断变化的第N个数作为因子参与计算,这保证了结果的随机性。

虽然通过我们的随机数看起来也很随机了,但必定这个算法是伪随机数,当第N个数和因子都相同时,生成的随机数仍然是重复的随机数,由于Random提供一个带参的构造函数允许我们传入一个因子,如果传入的因子随机性强的话,那么生成的随机数也会比较可靠,为了提供一个可靠点的因子,我们通常使用GUID产生填充因子,同样放在循环中测试:

for (int i = 0; i < 5; i++) {     byte[] buffer = Guid.NewGuid().ToByteArray();     int iSeed = BitConverter.ToInt32(buffer, 0);     Random random = new Random(iSeed);     Console.WriteLine(random.Next()); }

这样的方式保证了填充因子的随机性,所以生成的随机数也比较可靠,运行结果如下所示:

734397360 1712793171 1984332878 819811856 1015979983

在一些场景下这样的随机数并不可靠,为了生成更加可靠的随机数,微软在System.Security.Cryptography命名空间下提供一个名为RNGCryptoServiceProvider的类,它采用系统当前的硬件信息、进程信息、线程信息、系统启动时间和当前精确时间作为填充因子,通过更好的算法生成高质量的随机数,它的使用方法如下所示:

byte[] randomBytes = new byte[4]; RNGCryptoServiceProvider rngServiceProvider = new RNGCryptoServiceProvider(); rngServiceProvider.GetBytes(randomBytes); Int32 result = BitConverter.ToInt32(randomBytes, 0);

通过这种算法生成的随机数,经过成千上万次的测试,并未发现重复,质量的确比Random高了很多。另外windows api也提供了一个非托管的随机数生成函数CryptGenRandom,CryptGenRandom与RNGCryptoServiceProvider的原理类似,采用C++编写,如果要在.NET中使用,需要进行简单的封装。它的原型如下所示:

BOOL WINAPI CryptGenRandom(   _In_     HCRYPTPROV hProv,   _In_     DWORD dwLen,   _Inout_  BYTE *pbBuffer );

以上就是零度为您带来的随机数生成方法和基本原理,您可以通过需求和场景选择最佳的方式,Random算法简单,性能较高,适用于随机性要求不高的情况,由于RNGCryptoServiceProvider在生成期间需要查询上面提到的几种系统因子,所以性能稍弱于Random类,但随机数质量高,可靠性更好。

C#中生成的随机数为什么不随机?的更多相关文章

  1. sql语句中生成0-10随机数

    DECLARE @i int=0;DECLARE @j decimal(9,2);DECLARE @qnum INT=1000; SET NOCOUNT ONCREATE TABLE #temp_Ta ...

  2. Oracle中生成随机数的函数(转载)

    在Oracle中的DBMS_RANDOM程序包中封装了一些生成随机数和随机字符串的函数,其中常用的有以下两个: DBMS_RANDOM.VALUE函数 该函数用来产生一个随机数,有两种用法: 1. 产 ...

  3. JS生成指定范围内的随机数(支持随机小数)

    直接需要函数的话,直接到文章的最后面找. ============================================================= 转载:https://www.cn ...

  4. 高并发分布式系统中生成全局唯一(订单号)Id js返回上一页并刷新、返回上一页、自动刷新页面 父页面操作嵌套iframe子页面的HTML标签元素 .net判断System.Data.DataRow中是否包含某列 .Net使用system.Security.Cryptography.RNGCryptoServiceProvider类与System.Random类生成随机数

    高并发分布式系统中生成全局唯一(订单号)Id   1.GUID数据因毫无规律可言造成索引效率低下,影响了系统的性能,那么通过组合的方式,保留GUID的10个字节,用另6个字节表示GUID生成的时间(D ...

  5. 在Sqlserver中生成随机数据

    百度了各种随机生成,集中摘录如下: 一.循环写入千万级测试数据 DECLARE @i int ) BEGIN INSERT INTO A_User(username,password,addtime, ...

  6. Python中生成随机数

    目录 1. random模块 1.1 设置随机种子 1.2 random模块中的方法 1.3 使用:生成整形随机数 1.3 使用:生成序列随机数 1.4 使用:生成随机实值分布 2. numpy.ra ...

  7. ios 中生成随机数

    ios 有如下三种随机数方法: 1.    srand((unsigned)time(0));  //不加这句每次产生的随机数不变        int i = rand() % 5; 2.    s ...

  8. JAVA中生成指定位数随机数的方法总结

    JAVA中生成指定位数随机数的方法很多,下面列举几种比较常用的方法. 方法一.通过Math类 public static String getRandom1(int len) { int rs = ( ...

  9. Oracle中生成随机数的函数

    在Oracle中的DBMS_RANDOM程序包中封装了一些生成随机数和随机字符串的函数,其中常用的有以下两个: DBMS_RANDOM.VALUE函数 该函数用来产生一个随机数,有两种用法: 1. 产 ...

随机推荐

  1. Linux 配置SFTP,配置用户访问权限

    之前我服务器是使用的Windows Server 2003,这段时间由于访问量变大我还是机智的换成Linux了,在搭建FTP的时候看到网上都是推荐vsftpd,不过我不推荐这个家伙,看官且看下文. 我 ...

  2. qemu无界面启动,并重定向输出到终端

    qemu-system-x86_64  -kernel bzImage -initrd /mnt/rootfs.cpio.gz  /dev/zero -m 2G -nographic -append ...

  3. python3实现字符串的全排列的方法(无重复字符)

    https://www.jb51.net/article/143357.htm 抛出问题 求任意一个字符串的全排列组合,例如a='123',输出 123,132,213,231,312,321.(暂时 ...

  4. idea tomcat控制台system.out.println是乱码

    配置一下tomcat的信息.然后设置VM options.添加:-Dfile.encoding=UTF-8

  5. 【pyqtgraph绘图】线条,填充和颜色

    解读官方API-线条,填充和颜色 参考: http://www.pyqtgraph.org/documentation/style.html 线条,填充和颜色 Qt依靠其QColor,QPen和QBr ...

  6. 【PyQt5-Qt Designer】Qt 的标准对话框总结

    PyQt5 各种弹出对话框的总结 忙碌了两天才总结完,深刻体会到 “编程在实践中才能领悟更深”,后续有了更多的 理解继续来补充... 效果如下: 参考: https://www.cnblogs.com ...

  7. 求助!使用 ReportViewer 控件集成 Reporting Services2008 时,报"...401 unauthorized"错误!

    实现接口 public class ReportServiceCredetials : Microsoft.Reporting.WebForms.IReportServerCredentials { ...

  8. 筛选BETWEEN '2018-1-1 00:00:00' AND '2018-5-18 00:00:00'每日`status`='1'的记录总条数

    最近做了一个小任务,要求是:使用MySQL #筛选BETWEEN '2018-1-1 00:00:00' AND '2018-5-18 00:00:00'每日`status`='1'的记录总条数 SE ...

  9. 关于ARMv8另外几个问题

    版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/qianlong4526888/article/details/27510675 问题1:支持ARMv ...

  10. 20180925 SQL Server游标使用

    之前写了一个存储过程 目标: 根据时间,获取指定范围的数据集A, 再从数据集A 中,取出每行数据中的两个时间,卡另外一个B表里面的某列时间范围的值集.得到均值进行配置值比对. 这里有个问题,就是取得B ...