65.9K
CodeProject 正在变化。 阅读更多。
Home

随机数生成

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.76/5 (12投票s)

2011年3月2日

CPOL

4分钟阅读

viewsIcon

110626

.NET Framework 内置的生成器。

引言

本文将探讨随机数生成。虽然它不是一个热门话题,但随机数生成有许多实际用途,尤其是在密码学领域。 .NET Framework 提供的随机数生成器通过 System.Random 类实现。默认情况下,Random() 类的无参构造函数使用计算机的内部系统时钟生成自己的种子值,而带参构造函数可以接受 Int32 整数允许范围内的特定种子值。根据 Joe Duffy 在其著作《Professional .NET Framework 2.0》中的说法,这些生成的数字并不是真正随机的,因为该类使用确定性算法来生成它们。这意味着算法将按照相同的分步指令来生成序列中的下一个数字。它总是会根据“Random 算法的内在特性”从一个特定数字进行固定的转换到下一个。例如,如果您看到数字 553,您将始终知道下一个数字是 1347421470。为什么?请查看这段代码

using System;
class App {
    static void Main() {
        Random r = new Random(553);
        Console.WriteLine(r.Next());
    }
}

运行这段代码,输出是 1347421470。这个问题也可能源于系统时钟的工作机制。系统时钟的值在不断变化,因此使用无参构造函数每次调用时都会启动一个不同的数值序列。然而,由于时钟的分辨率是有限的,在短时间内使用无参构造函数创建不同的 Random() 对象可能会生成产生相同随机数值序列的随机数生成器。这个问题可以通过创建一个 Random() 对象来随着时间的推移生成大量随机数来避免,而不是反复创建多个新的 Random() 对象来一次生成一个随机数。更确切地说,如果您一个接一个地创建两个 Random 对象,它们实际上可能具有相同的基于时间的种子。

然而,随机数生成器的某些特性可以具有一定的实际用途。例如,如果您想重复相同的数字序列,您可以在实例化时通过向构造函数传递相同的整数值来设置种子状态。一旦创建了 Random() 对象,我们就可以调用 Next() 方法来生成一个随机数。Next() 方法有三种不同的重载方式。不带任何参数时,Next() 方法返回一个介于 0 到 int32.Max(即 2,147,483,647)之间的随机数。Next(int32) 方法返回一个介于 0 到由单个输入参数指定的 Int32 整数值之间的非负随机数。最后,Next(Int32,Int32) 方法返回一个介于两个输入参数指定的允许 Int32 整数范围内的任何随机数。Random.NextBytes() 方法用介于 0 到 255 之间的随机字节填充指定字节数组的元素。因此,Random() 类也可以用来填充一个具有随机字节的字节数组。话虽如此,让我们来看一些示例测试代码

using System;
public class App {
    public static void Main()
    {
        int seed = 345;
        int min = -44;
        int max = 30;
        Console.WriteLine("Testing 6 random Int32 from 0 to Int32.MAX");
        Random r1 = new Random(seed);
        for (int j = 0; j < 6; j++)
            Console.Write("{0,11} ", r1.Next());
        Console.WriteLine("Testing 6 random int32 from 0 to 30");
        Random r2 = new Random(seed);
        for (int j = 0; j < 6; j++)
            Console.Write("{0,11} ", r2.Next(max));
        Console.WriteLine("Testing 6 random int32 from -44 to 30");
        Random r3 = new Random(seed);
        for (int j = 0; j < 6; j++)
            Console.Write("{0,11} ", r3.Next(min, max));
        Console.WriteLine("Testing 5 random bytes: 0 to 255");
        Random r4 = new Random();
        Byte[] b = new Byte[10];
        r4.NextBytes(b);
        Console.WriteLine("The Random bytes are: ");
        for (int i = 0; i < 10; i++)
        {
            Console.Write(i);
            Console.Write(":");
            Console.WriteLine(b[i]);
        }
        Console.WriteLine("Testing 6 random doubles from 0 to 1");
        Random r5 = new Random(seed);
        for (int j = 0; j < 6; j++)
            Console.WriteLine("{0,11} ",r5.NextDouble());
        Console.WriteLine("Testing 6 random doubles from -44 to 30");
        Random r6 = new Random(seed);
        for (int j = 0; j < 6; j++)
            Console.WriteLine("{0,11}",(min+(max-min)*r6.NextDouble()));
    }
}

这段代码的输出是

Testing 6 random Int32 from 0 to Int32.MAX
 2067976641   100391485  1973278494   420847188  2080081379   115165468 Testing
6 random int32 from 0 to 30
         28           1          27           5          29           1 Testing
6 random int32 from -44 to 30
         27         -41          23         -30          27         -41 Testing
5 random bytes: 0 to 255
The Random bytes are:
0:226
1:229
2:112
3:209
4:255
5:43
6:96
7:12
8:76
9:58
Testing 6 random doubles from 0 to 1
0.962976665218816
0.0467484281616045
0.918879404160604
0.195972243415179
0.968613373101043
0.0536281001072508
Testing 6 random doubles from -44 to 30
27.2602732261924
-40.5406163160413
23.9970759078847
-29.4980539872768
27.6773896094772
-40.0315205920634

连续创建 Random() 对象可以通过解决系统时钟和确定性算法相关的问题来产生结果。如果您在处理密码学算法时需要一个强大的、加密安全的随机数生成器,System.Security.Cryptography.RandomNumberGenerator 类实现了此类算法。请查看这段代码

using System;
using System.Security.Cryptography;
class Program {
    static void Main(string[] args) {
        // create a new number generator
        RandomNumberGenerator rng = RandomNumberGenerator.Create();
        // define a byte array to fill with random data
        byte[] randomData = new byte[50];
        // generate random data
        rng.GetBytes(randomData);
        // print out the data
        Console.WriteLine(BitConverter.ToString(randomData));
        // wait for input before exiting
        Console.WriteLine("Press enter to finish");
        Console.ReadLine();
    }
}

该类是 abstract 的,但提供了一个 static 工厂方法 Create(),该方法返回一个新的 RandomNumberGenerator 实例。这是这段代码的输出

C1-38-8F-EB-1E-74-BA-E4-59-47-06-B3-BA-97-2C-91-0A-8D-B4-11-82-8C-48-84-AD-E6-D7
-C9-23-AE-AE-1A-F4-1C-D4-59-8B-E0-64-1B-50-7F-52-2E-DB-90-0B-91-F0-8E
Press enter to finish

随机密码生成

为便于论证,我们假设密码仅通过字母数字字符生成。字母表中有 26 个字母。小写和大写字母总共是 52 个字符。数字 0-9 共 10 个,总计 62 个字符。这会导致一个 long string 声明,但让我们试试

using System;
using System.Threading;
public  class App {
    private static string MakePassword(int pl)
    {
        string possibles = "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789";
        char[] passwords = new char[pl];
        Random rd = new Random();

        for (int i = 0; i < pl; i++)
        {
            passwords[i] = possibles[rd.Next(0, possibles.Length)];
        }
        return new string(passwords);
    }

    public static void Main()
    {
        Console.WriteLine("Slow Test for Random Password Generation");
        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine("Password {0}={1}",i, MakePassword(10));
            Thread.Sleep(2000);
        }
        Console.WriteLine("Press the Enter Key to finish...");
        Console.ReadLine();
    }
}

注意:上面的代码片段应写成并行化循环,但本文的重点是检查随机数生成。一旦我们对该问题有了牢固的理解,我们就可以查看代码并确定哪些代码块可以并行化以及使用哪种类型的并行构造。这是上面代码的输出

Slow Test for Random Password Generation
Password 0=27OO8zn8Fh
Password 1=auaexRRxAc
Password 2=O8eUn0OqDv
Password 3=VuBmMhhOyr
Password 4=z9E1BrdHAJ
Password 5=Gv1sZJJ5vE
Password 6=OSoUo0ctrA
Password 7=swszda8ntT
Password 8=zSP1CsDLoO
Password 9=cwSFsBzEr6
Press the Enter Key to finish...

一个有争议的随机数生成器:梅森旋转算法

下面的代码引用自梅森旋转算法的片段。这个随机数生成器因其代码的复杂性而受到学术界和科学界的批评。同时,它也被誉为一个强大的程序。读者可以下载 C 或 C++ 版本的代码。这是一个经过编辑以使其能够编译的 C# 版本

using System;
public class MersenneTwister
// Class MersenneTwister generates random numbers
// from a uniform distribution using the Mersenne
// Twister algorithm.
private const int N = 624;
private const int M = 397;
private const uint MATRIX_A = 0x9908b0dfU;
private const uint UPPER_MASK = 0x80000000U;
private const uint LOWER_MASK = 0x7fffffffU;
private const int MAX_RAND_INT = 0x7fffffff;
private uint[] mag01 = {0x0U, MATRIX_A};
private uint[] mt = new uint[N];
private int mti = N+1;
public MersenneTwister()
{ init_genrand( (uint)DateTime.Now.Millisecond); }
public MersenneTwister( int seed )
{
    init_genrand( (uint)seed );
}

public MersenneTwister( int[] init )
{
    uint[] initArray = new uint[init.Length];
    for ( int i = 0; i < init.Length; ++i )
    initArray[i] = (uint)init[i];
    init_by_array( initArray, (uint)initArray.Length);
}
public static int MaxRandomInt
{ get { return 0x7fffffff; } }
public int Next()
{ return genrand_int31(); }
public int Next( int maxValue )
{ return Next( 0, maxValue ); }
public int Next( int minValue, int maxValue )
{
    if ( minValue > maxValue )
    {
        int tmp = maxValue;
        maxValue = minValue;
        minValue = tmp;
    }
    return (int)(Math.Floor((maxValue-minValue+1)*genrand_real1()+
    minValue));
}
public float NextFloat()
{ return (float) genrand_real2(); }
public float NextFloat( bool includeOne )
{
    if ( includeOne )
    {
        return (float) genrand_real1();
    }
    return (float) genrand_real2();
}
public float NextFloatPositive()
{ return (float) genrand_real3(); }
public double NextDouble()
{ return genrand_real2(); }
public double NextDouble( bool includeOne )
{
    if ( includeOne )
    {
        return genrand_real1();
    }
    return genrand_real2();
}

public double NextDoublePositive()
{ return genrand_real3(); }
public double Next53BitRes()
{ return genrand_res53(); }
public void Initialize()
{ init_genrand((uint)DateTime.Now.Millisecond); }
public void Initialize( int seed )
{ init_genrand( (uint)seed ); }
public void Initialize( int[] init )
{
    uint[] initArray = new uint[init.Length];
    for ( int i = 0; i < init.Length; ++i )
    initArray[i] = (uint)init[i];
    init_by_array( initArray, (uint)initArray.Length );
}
private void init_genrand( uint s)
{
    mt[0]= s & 0xffffffffU;
    for (mti=1; mti<N; mti++)
    {
        mt[mti] = (uint)(1812433253U*(mt[mti-1]^(mt[mti-1]>>30))+mti);
        mt[mti] &= 0xffffffffU;
    }
}
private void init_by_array(uint[] init_key, uint key_length)
{
    int i, j, k;
    init_genrand(19650218U);
    i=1; j=0;
    k = (int)(N>key_length ? N : key_length);
    for (; k>0; k--)
    {
        mt[i] = (uint)((uint)(mt[i]^((mt[i-1]^(mt[i-1]>>30))*1664525U))+init_key[j]+j);
        mt[i] &= 0xffffffffU;
        i++; j++;
        if (i>=N) { mt[0] = mt[N-1]; i=1; }
        if (j>=key_length) j=0;
    }
    for (k=N-1; k>0; k--)
    {
        mt[i] = (uint)((uint)(mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) *
        1566083941U))- i);
        mt[i] &= 0xffffffffU;
        i++;
        if (i>=N) { mt[0] = mt[N-1]; i=1; }
    }
    mt[0] = 0x80000000U;
}

uint genrand_int32()
{
    uint y;
    if (mti >= N)
    {
        int kk;
        if (mti == N+1)
        init_genrand(5489U);
        for (kk=0;kk<N-M;kk++)
        {
            y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
            mt[kk] = mt[kk+M] ^ (y >> 1) ^ mag01[y & 0x1U];
        }
        for (;kk<N-1;kk++)
        {
            y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
            mt[kk] = mt[kk+(M-N)] ^ (y >> 1) ^ mag01[y & 0x1U];
        }
        y = (mt[N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK);
        mt[N-1] = mt[M-1] ^ (y >> 1) ^ mag01[y & 0x1U];
        mti = 0;
    }
    y = mt[mti++];
    y ^= (y >> 11);
    y ^= (y << 7) & 0x9d2c5680U;
    y ^= (y << 15) & 0xefc60000U;
    y ^= (y >> 18);
    return y;
}
private int genrand_int31()
{ return (int)(genrand_int32()>>1); }
double genrand_real1()
{ return genrand_int32()*(1.0/4294967295.0); }
double genrand_real2()
{ return genrand_int32()*(1.0/4294967296.0); }
double genrand_real3()
{
    return (((double)genrand_int32())+0.5)*(1.0/4294967296.0);}
    double genrand_res53()
    {
        uint a=genrand_int32()>>5, b=genrand_int32()>>6;
        return(a*67108864.0+b)*(1.0/9007199254740992.0);
    }
}
public class Program {
    static void Main()
    {
        MersenneTwister randGen = new MersenneTwister();
        Console.WriteLine( "100 uniform random integers in [0,{0}]:",
        MersenneTwister.MaxRandomInt);
        int i;

        for (i = 0; i < 100; ++i)
        {
            Console.Write("{0} ",randGen.Next());
            if ( i%5 == 4 ) Console.WriteLine("");
        }
        Console.WriteLine("100 uniform random doubles in [0,1]:");
        for ( i = 0; i < 100; ++i )
        {
            Console.Write("{0} ",randGen.NextDouble().ToString("F8"));
            if ( i%5 == 4 ) Console.WriteLine("");
        }
        Console.WriteLine("Press ENTER to quit");
        Console.ReadLine();
    }
}

输出如下

100 uniform random integers in [0,2147483647]:
1075176619 857754206 1005167141 991201697 1075422282
1747626860 320357673 932416052 1761533440 1877355339
690124008 394204523 1673263413 1306605477 1750371637
131189303 31787654 55688665 488723751 172092270
1528431908 2143696765 1763326522 570125855 1703653473
1943673222 1063468238 979779157 1203824982 2138479727
1105805415 51695726 616356430 1907769139 403919762
546097329 732304019 1426151751 571894936 1787565766
435528614 2053456611 875459441 1223172521 1119364174
641508640 18152413 91285243 2068336737 1782242804
1847854439 1426078730 1696565377 220195592 1615216442
1311150674 2016878752 1411646858 2113603839 387896851
182685019 452901650 1143849234 1947560626 1623179537
1793681409 666445687 455322196 1485561736 2016878799
419516289 1492722441 1834382965 1304567204 940786356
159127256 1362751565 668535681 599232874 816298036
1449977427 1771450370 824382733 1749254406 456991468
1189135967 362909535 111507413 949147242 1370981469
1984562805 1397068659 1697759068 1093507400 691950388
2036212955 9845334 610019413 1477955157 1158880977
100 uniform random doubles in [0,1]:
0.64427878 0.58951317 0.40004608 0.76980695 0.00424254
0.21054363 0.57517565 0.89644978 0.22379096 0.67868496
0.85624201 0.02531357 0.79852453 0.38303446 0.73290709
0.75898620 0.88415779 0.38217626 0.56409770 0.37470631
0.65536917 0.48810426 0.05990990 0.38777581 0.80498216
0.32344267 0.78534319 0.09117531 0.95645391 0.62211520
0.47396311 0.88660040 0.72666519 0.62785680 0.67998362
0.06089570 0.66838163 0.61557270 0.05300447 0.80103798
0.60875345 0.88096833 0.33373527 0.95487843 0.93682441
0.84364382 0.00884743 0.07577453 0.92769417 0.05884617
0.21523187 0.15010924 0.54033703 0.83325611 0.49797325
0.04532573 0.65437883 0.63682698 0.76614741 0.82909524
0.84164811 0.21053670 0.27436116 0.23372914 0.47964557
0.34993759 0.71205157 0.93345212 0.24736210 0.88091276
0.65001251 0.04091075 0.81341497 0.88265975 0.69665167
0.81644969 0.63767136 0.53178586 0.89998561 0.39706215
0.47618763 0.15616494 0.19274149 0.46349037 0.49884292
0.99134203 0.00503471 0.97789549 0.74999581 0.79177777
0.35976462 0.90491676 0.02709002 0.01703469 0.28894546
0.96114512 0.71049253 0.89851955 0.61757205 0.15242437
Press ENTER to quit

2 的 31 次方是 2147483647。上述算法能够生成 100 个介于 0 到 2147483647 之间的随机整数。后面一部分能够生成 100 个介于 0 和 1 之间的 double 值。总而言之,随机数生成在某些应用数学领域扮演着不可或缺的角色。然而,对于 .NET 开发人员来说,使用 System.Security.Cryptography 命名空间中包含的类可能更安全。

© . All rights reserved.