在程序設計過程中,我們經常需要用到不同的隨機數序列,于是我們寫下了這樣的程序:
//TickCount.CS
public class MainClass
{
public static void Main()
{
for(int i=0; i<10; i++)//生成10個隨機序列
{
CreateRand();
}
}
public static void CreateRand()
{
Random random = new Random();
for(int i=0;i<6;i++)//6個數字的隨機序列
Console.Write(string.Format("{0} ",random.Next()));
Console.WriteLine();
}
}然而不幸的是,我們卻得到了幾個相同的序列,在我的計算機上(AMD 1.49GHz CPU,512M內存,WinXp+Sp2)編譯和運行結果如下:
E:CSC>csc tickcount.cs
Microsoft (R) Visual C# 2005 編譯器 版本 8.00.50727.42
用于 Microsoft (R) Windows (R) 2005 Framework 版本 2.0.50727
版權所有 (C) Microsoft Corporation 2001-2005。保留所有權利。
E:CSC>tickcount.exe
733598421 1904084089 330766232 1331029161 1336728080 1887876763
733598421 1904084089 330766232 1331029161 1336728080 1887876763
733598421 1904084089 330766232 1331029161 1336728080 1887876763
733598421 1904084089 330766232 1331029161 1336728080 1887876763
733598421 1904084089 330766232 1331029161 1336728080 1887876763
733598421 1904084089 330766232 1331029161 1336728080 1887876763
733598421 1904084089 330766232 1331029161 1336728080 1887876763
733598421 1904084089 330766232 1331029161 1336728080 1887876763
1215178376 1762749978 309033493 1619713897 294892275 1327336430
1215178376 1762749978 309033493 1619713897 294892275 1327336430前8個“隨機”序列是相同的,后2個“隨機”序列也是相同的,這不是我們希望得到的結果,我們希望得到的是互不相同的隨機數序列!為什么會得到相同的序列呢?究其原因,就是Random類只是一個偽隨機數生成器,并不能產生"絕對隨機"的隨機數。這里“偽”不是表示“假”的意思,而是表示“有規律”的意思,也就是說:計算機產生的偽隨機數既是隨機的又是有規律的。
偽隨機數(有庫函數產生)與“理想中的”“真”隨機數不同,偽隨機數是由可確定的(deterministic)函數產生,雖然隨機函數可以產生有隨機特征的數字序列,但這些數字并不不具備真隨機數的一些特性,并非統計意義上的隨機數。偽隨機數是可以確定的:知道序列中的一個數就可以獲得其他剩下數字的有關信息;事實上,如果知道了序列的初始值(種子)通常可以確定整個序列。記得大一上計算機專業基礎課的第一節課上,老師就給我們介紹了計算機程序的5個特性(詳見附1),其中的一點就是確定性,即“對于相同的輸入只能得出相同的輸出”,偽隨機數的生成正是符合這條金科玉律。下面我們來觀察偽隨機數生成器Random的兩個構造函數:
public Random() : this(Environment.TickCount){}//默認構造函數,以TickCount作為隨機數種子
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;
}
}
}//根據程序的確定性,顯然傳入相同的Seed,將得到同樣的SeedArray
this.inext = 0;
this.inextp = 0x15;
Seed = 1;
} Random類的默認構造函數中,以系統啟動后經過的毫秒數TickCount作為隨機數種子,在MSDN中,針對Environment.TickCount有這樣一句注釋:“TickCount 屬性的分辨率不能小于500毫秒。”。也就是說:在0.5秒之內,我們將得到相等的TickCount值;再啰嗦點兒就是說:在0.5秒之內通過默認的構造函數構造的所有Random實例都有相同的SeedArray,相同的數據源在同一臺計算機上經過相同的算法處理后,理所當然應該得出相同的結果序列了^_^。
雖然MSDN中說“TickCount屬性的分辨率不能小于500毫秒”,但我在自己的機器上測試了一下,結果略有偏差,下面是測試代碼和編譯運行結果:
//TickCount2.CS
using System;
public class MainClass
{
public static void Main()
{
long start;
long end = start = Environment.TickCount;
while(end == start) //測試系統能分辨的TickCount的取值間隔
{
end = Environment.TickCount;
}
Console.Write("Ticks:");
Console.WriteLine(end - start);
DateTime startTime;
DateTime endTime = startTime = DateTime.Now;
while(endTime == startTime)//測試系統能分辨的DateTime的取值間隔
{
endTime = DateTime.Now;
}
Console.Write("Time:");
Console.WriteLine(endTime - startTime);
}
}
//在我的計算機上編譯和運行結果如下:
E:CSC>csc tickcount2.cs
Microsoft (R) Visual C# 2005 編譯器 版本 8.00.50727.42
用于 Microsoft (R) Windows (R) 2005 Framework 版本 2.0.50727
版權所有 (C) Microsoft Corporation 2001-2005。保留所有權利。
E:CSC>tickcount2.exe
Ticks:10
Time:00:00:00.0100144啊哈,我的機器上TickCount的分辨率約為10毫秒(0.0100144秒)^_^
說了這么多,我們了解到在一段很小的時間段內(我的機器上約是10毫秒),由默認構造函數生成的不同Random實例將產生相同的“隨機”數(或“隨機”數序列)。現在回到文章開頭的問題上,如果我們想在很短的時間內得到不同的“隨機”數序列,該怎么辦呢?一個解決辦法就是在短時間內不要構造不同的Random實例,而是使用同一個Random實例,代碼示例(對TickCount1.CS稍加修改)和編譯運行結果如下:
//TickCount3.CS
using System;
public class MainClass
{
public static void Main()
{
Random random = new Random();
for(int i=0; i<10; i++)//生成10個隨機序列
{
CreateRand(random);
}
}
public static void CreateRand(Random random)
{
for(int i=0;i<6;i++)//6個數字的隨機序列
Console.Write(string.Format("{0} ",random.Next()));
Console.WriteLine();
}
}
/// 程序運行編譯和運行結果如下:
E:CSC>csc tickcount3.cs
Microsoft (R) Visual C# 2005 編譯器 版本 8.00.50727.42
用于 Microsoft (R) Windows (R) 2005 Framework 版本 2.0.50727
版權所有 (C) Microsoft Corporation 2001-2005。保留所有權利。
E:CSC>tickcount3.exe
1881514665 187597856 1876537022 2146319285 138059684 807943728
576262772 779063600 1237870363 742301708 2044162198 1694855412
2005040916 325723903 736394631 133990481 1511650304 169478959
686369588 305867187 1159984149 953908396 1685499642 741064817
1282574331 555617587 1241463657 736349125 596033346 794494845
780261119 120040711 1518990931 565960940 45469235 1597098033
1712262257 825399914 282791381 388999071 1341257075 448146946
633252151 635828560 2022035278 1352429249 463708613 1214940829
439459392 1663640291 751889087 1823526590 292520889 1908503776
1336657042 927606269 649581861 1135472205 863744954 1729925744
這樣的結果“從一定程度上”符合了“在短時間內生成不同的隨機數序列”,然而,事情遠沒有這么簡單:這里我用引號強調了只是從“一定程度上”符合而已,實際上并不完全符合“隨機”的要求。要想完全符合“隨機”的要求(例如在加密程序中),必須使用真隨機數!真隨機數與偽隨機數相反,其生成過程必須獨立于它的生成函數,所以即使知道了真隨機數序列中的一些數,外界也無法推算序列中的其他數。
附1 - 計算機程序的5個特性:
有窮性:一個算法必須總是(對任何合法的輸入值)在執行有窮步之后結束,且每一步都可在有窮時間內完成;
確定性:算法中每一條指令必須有確切的含義,讀者理解時不會產生二義性。有任何條件下,算法只有唯一的一條執行路徑,即對于相同的輸入只能得出相同的輸出。
可行性:一個算法是能行的,即算法中描述的操作都是可以通過已經實現的基本運算執行有限次來實現的。
輸入:一個算法有零個或多個的輸入,這些輸入取自于某個特定的對象的集合。
輸出:一個算法有一個或多個的輸出。這些輸出是同輸入有著某些特定關系的量。
附2 - Random類源代碼(經Reflector.exe反匯編得到):
namespace System
{
using System.Globalization;
using System.Runtime.InteropServices;
[Serializable, ComVisible(true)]
public class Random
{
private int inext;
private int inextp;
private const int MBIG = 0x7fffffff;//0111 1111 1111 1111 1111 1111 1111 1111
private const int MSEED = 0x9a4ec86;//0000 1001 1010 0100 1110 1100 1000 0110
private const int MZ = 0;
private int[] SeedArray;
public Random() : this(Environment.TickCount)
{
}
/*
* Environment.TickCount方法
* public static int TickCount
* {
* get
* {
* return nativeGetTickCount();
* }
* }
*
* [MethodImpl(MethodImplOptions.InternalCall)]
* private static extern int nativeGetTickCount();
*/
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;
}
}
}//傳入相同的Seed,將得到同樣的SeedArray
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;
return (num2 / 4294967293);
}
private int InternalSample()
{
int index = this.inext;
int inextp = this.inextp;
if (++index >= 0x38)
{
index = 1;
}
if (++inextp >= 0x38)
{
inextp = 1;
}
int num = this.SeedArray[index] - this.SeedArray[inextp];
if (num < 0)
{
num += 0x7fffffff;
}
this.SeedArray[index] = num;
this.inext = index;
this.inextp = inextp;
return num;
}
public virtual int Next()
{
return this.InternalSample();
}
public virtual int Next(int maxValue)
{
if (maxValue < 0)
{
throw new ArgumentOutOfRangeException("maxValue", string.Format(CultureInfo.CurrentCulture, Environment.GetResourceString("ArgumentOutOfRange_MustBePositive"), new object[] { "maxValue" }));
}
return (int) (this.Sample() * maxValue);
}
public virtual int Next(int minValue, int maxValue)
{
if (minValue > maxValue)
{
throw new ArgumentOutOfRangeException("minValue", string.Format(CultureInfo.CurrentCulture, Environment.GetResourceString("Argument_MinMaxValue"), new object[] { "minValue", "maxValue" }));
}
long num = maxValue - minValue;
if (num <= 0x7fffffff)
{
return (((int) (this.Sample() * num)) + minValue);
}
return (((int) ((long) (this.GetSampleForLargeRange() * num))) + minValue);
}
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);
}
}
public virtual double NextDouble()
{
return this.Sample();
}
protected virtual double Sample()
{
return (this.InternalSample() * 4.6566128752457969E-10);
}
}
}

浙公網安備 33010602011771號