.NET Framework:随机数生成器生成重复模式

编辑:这不是一个重复的,它不是天真的误解如何使用随机数字发生器的结果。 谢谢。

我似乎已经发现了由System.Random类生成的数字的重复模式。 我正在使用“主”随机实例生成第二个“主”随机实例的种子。 这个主要随机实例生成的值呈现重复模式。 特别是第三个数字是非常可预测的。

下面的程序演示了这个问题。 请注意,每次在循环中使用不同的种子值。

using System; class Program { static void Main(string[] args) { // repeat experiment with different master RNGs for (int iMaster = 0; iMaster < 30; ++iMaster) { // create master RNG var rngMaster = new Random(iMaster + OFFSET); // obtain seed from master RNG var seed = rngMaster.Next(); // create main RNG from seed var rngMain = new Random(seed); // print 3rd number generated by main RNG var ignore0 = rngMain.Next(LIMIT); var ignore1 = rngMain.Next(LIMIT); var randomNumber = rngMain.Next(LIMIT); Console.WriteLine(randomNumber); } } const int OFFSET = 0; const int LIMIT = 200; } 

我认为这应该产生随机的输出,但我的盒子上的实际输出是:

 84 84 84 84 84 84 84 84 84 84 84 ... 

任何人都可以解释这里发生了什么? 改变OFFSET和LIMIT常量会改变输出值,但它总是重复。

欢迎来到非密码强大的RNG的世界。 显然内置的.NET RNG有一个倾向,使其输出第三个数字84,如果你把它的输出限制在0到200之间。 看看下面这个版本的程序,它会显示更多的输出内容。

 class Program { static void Main(string[] args) { Console.WindowWidth = 44; Console.WindowHeight = 33; Console.BufferWidth = Console.WindowWidth; Console.BufferHeight = Console.WindowHeight; string template = "|{0,-5}|{1,-11}|{2,-5}|{3,-5}|{4,-5}|{5,-5}|"; Console.WriteLine(template, "s1", "s2", "out1", "out2", "out3", "out4"); Console.WriteLine(template, new String('-', 5), new String('-', 11), new String('-', 5), new String('-', 5), new String('-', 5), new String('-', 5)); // repeat experiment with different master RNGs for (int iMaster = 0; iMaster < 30; ++iMaster) { int s1 = iMaster + OFFSET; // create master RNG var rngMaster = new Random(s1); // obtain seed from master RNG var s2 = rngMaster.Next(); // create main RNG from seed var rngMain = new Random(s2); var out1 = rngMain.Next(LIMIT); var out2 = rngMain.Next(LIMIT); var out3 = rngMain.Next(LIMIT); var out4 = rngMain.Next(LIMIT); Console.WriteLine(template, s1, s2, out1, out2, out3, out4); } Console.ReadLine(); } const int OFFSET = 0; const int LIMIT = 200; } 

这是输出

 | s1 | s2 | out1 | out2 | out3 | out4 |
 | ----- | ----------- | ----- | ----- | ----- | ----- |
 | 0 | 1559595546 | 170 | 184 | 84 | 84 |
 | 1 | 534011718 | 56 | 177 | 84 | 123 |
 | 2 | 1655911537 | 142 | 171 | 84 | 161 |
 | 3 | 630327709 | 28 | 164 | 84 | 199 |
 | 4 | 1752227528 | 114 | 157 | 84 | 37 |
 | 5 | 726643700 | 0 | 150 | 84 | 75 |
 | 6 | 1848543519 | 86 | 143 | 84 | 113 |
 | 7 | 822959691 | 172 | 136 | 84 | 151 |
 | 8 | 1944859510 | 58 | 129 | 84 | 189 |
 | 9 | 919275682 | 144 | 122 | 84 | 28 |
 | 10 | 2041175501 | 30 | 115 | 84 | 66 |
 | 11 | 1015591673 | 116 | 108 | 84 | 104 |
 | 12 | 2137491492 | 2 | 102 | 84 | 142 |
 | 13 | 1111907664 | 88 | 95 | 84 | 180 |
 | 14 | 86323836 | 174 | 88 | 84 | 18 |
 | 15 | 1208223655 | 60 | 81 | 84 | 56 |
 | 16 | 182639827 | 146 | 74 | 84 | 94 |
 | 17 | 1304539646 | 31 | 67 | 84 | 133 |
 | 18 | 278955818 | 117 | 60 | 84 | 171 |
 | 19 | 1400855637 | 3 | 53 | 84 | 9 |
 | 20 | 375271809 | 89 | 46 | 84 | 47 |
 | 21 | 1497171628 | 175 | 40 | 84 | 85 |
 | 22 | 471587800 | 61 | 33 | 84 | 123 |
 | 23 | 1593487619 | 147 | 26 | 84 | 161 |
 | 24 | 567903791 | 33 | 19 | 84 | 199 |
 | 25 | 1689803610 | 119 | 12 | 84 | 38 |
 | 26 | 664219782 | 5 | 5 | 84 | 76 |
 | 27 | 1786119601 | 91 | 198 | 84 | 114 |
 | 28 | 760535773 | 177 | 191 | 84 | 152 |
 | 29 | 1882435592 | 63 | 184 | 84 | 190 |

所以主RND的第一个输出与第一个被链接的第二个RNG的前几个输出之间有很强的相关性。 Random RNG不是被devise成“安全的”,它被devise成“快速”,所以像你在这里看到的是快速和安全之间的权衡。 如果你不想要这样的事情发生,你需要使用一个密码安全的随机数发生器。

然而,只是切换到encryption随机数生成器(CRNG)是不够的,你仍然需要小心如何使用CRNG。 WEP无线安全发生了一个非常类似的问题。 根据头中给出的IV,可以预测随机数发生器的种子值(WEP密钥)是用来保护连接的。 虽然他们使用CRNG(他们使用RC4),但他们没有正确使用它(在输出变得不可预测之前,你必须吐出几千次迭代)。

自己运行代码之后,它看起来像返回的第三个元素的值 – 无论种子是一个非常糟糕的缺陷。 我修改你的代码如下,使它更灵活一点:

 public static void TestRNG() { const int OFFSET = 0; const int LIMIT = 200; const int RndCount = 50; const int FieldsPerLine = 10; int Rnd; for (int iMaster = 0; iMaster < RndCount; ++iMaster) { // create master RNG var rngMaster = new Random(iMaster + OFFSET); // obtain seed from master RNG var seed = rngMaster.Next(); // create main RNG from seed var rngMain = new Random(seed); // print 3rd number generated by main RNG Console.WriteLine(); for (int Loop = 0; Loop < FieldsPerLine; Loop++) { Rnd = rngMain.Next(LIMIT); Console.Write(Rnd.ToString().PadLeft(3) + " "); } } } 

输出如下所示:

 170 184 84 84 5 104 164 113 181 147 56 177 84 123 150 132 149 56 142 88 142 171 84 161 94 160 134 199 102 28 28 164 84 199 39 189 119 141 62 168 114 157 84 37 184 17 105 84 23 108 0 150 84 75 129 45 90 27 183 48 86 143 84 113 74 74 75 169 144 188 172 136 84 151 18 102 60 112 104 129 58 129 84 189 163 130 46 55 64 69 144 122 84 28 108 159 31 197 25 9 30 115 84 66 53 187 16 140 185 149 116 108 84 104 198 15 1 82 145 89 2 102 84 142 142 44 186 25 106 29 88 95 84 180 87 72 172 168 66 170 174 88 84 18 32 100 157 110 26 110 60 81 84 56 177 129 142 53 187 50 146 74 84 94 121 157 127 196 147 190 31 67 84 133 66 185 113 138 108 130 117 60 84 171 11 14 98 81 68 70 3 53 84 9 156 42 83 24 28 11 89 46 84 47 101 70 68 166 189 151 175 40 84 85 45 99 54 109 149 91 61 33 84 123 190 127 39 51 109 31 147 26 84 161 135 155 24 194 70 171 33 19 84 199 80 184 9 137 30 112 119 12 84 38 25 12 195 79 190 52 5 5 84 76 169 40 180 22 151 192 91 198 84 114 114 69 165 165 111 132 177 191 84 152 59 97 150 107 71 72 63 184 84 190 4 125 136 50 32 12 149 177 84 28 148 154 121 193 192 153 35 171 84 66 93 182 106 135 153 93 121 164 84 104 38 10 91 78 113 33 7 157 84 143 183 39 77 20 73 173 93 150 84 181 128 67 62 163 34 113 179 143 84 19 72 95 47 106 194 53 65 136 84 57 17 124 32 48 154 194 151 129 84 95 162 152 18 191 115 134 37 122 84 133 107 180 3 134 75 74 123 115 84 171 52 9 188 76 35 14 9 108 84 10 196 37 173 19 196 154 95 102 84 48 141 65 158 162 156 95 181 95 84 86 86 94 144 104 117 35 66 88 84 124 31 122 129 47 77 175 152 81 84 162 176 150 114 189 37 115 38 74 84 0 120 179 99 132 198 55 124 67 84 38 65 7 85 75 158 195 10 60 84 76 10 35 70 17 118 136 96 53 84 115 155 64 55 160 79 76 182 46 84 153 99 92 40 103 39 16 

我曾经看过过去没有使用从Random.Next方法返回的前三个或四个值的代码示例。 现在我知道为什么。 所以,一个简单的解决方法是丢弃从Random.Next方法返回的前4个值。

如果你对一个非常快速的随机数发生器感兴趣,那么也可以生成高质量的随机数,然后检查TinyMT项目 – 我从原始的C代码中移植出来。

这仅仅是一个评论,但它需要空间。 我只手动添加缩进和注释到SO文本框中的DotLisp代码。 除了是否使用(.Next (Random. i))作为种子,或者它只是使用i作为种子本身,以及是否检查第三个或第四个.Next随机数,代码是相同的。

我现在没有再检查,但我相信每个.Next x一个.Next x总是检索一个新的样本,并将结果转换为0x-1之间的东西。

使用x = (* 15 183) = 2745是因为看着更小的范围,更像10000种子,我发现了第三个.Next x是“统一的”随机的,但有两个“统一”的比率。 较小的select值的范围是大约177到190个连续的数字。 (你可以通过在最后一行调用(print-histo h)来看到这一点,但减lesstesting种子的数量:-))当我增加种子的数量,我增加了这个范围。

代码只是为每个.Next x结果累计一个计数,并显示这些计数的计数。 对于一个真正的统一的随机.Next x应该产生一个不错的钟形曲线,如上一个案例(第四个,下一个顺序种子)所看到的。

 (let (h (Hashtable.)) (dotimes i 6553600 (lets (s (.Next (Random. i)) r (Random. s)) ; using random seed (dotimes j 2 r.Next) ; skipping 2 results (let (x (r.Next (* 15 183))) (xh (+ 1 (or (xh) 0)))))) (print-histo (histo h.Values))) 
  1 2368 3 2369 11 2370 20 2371 11 2372 12 2373 17 2374 15 2375 8 2376 13 2377 20 2378 11 2379 3 2380 8 2382 94 2383 253 2384 296 2385 240 2386 248 2387 238 2388 233 2389 252 2390 236 2391 321 2392 163 2393 18 2394 

歪斜的钟形曲线和另一个小扁平的钟形曲线或只是非常不均匀的尾部。

 (let (h (Hashtable.)) (dotimes i 6553600 (lets (s (.Next (Random. i)) r (Random. s)) ; using random seed (dotimes j 3 r.Next) ; skipping 3 results (let (x (r.Next (* 15 183))) (xh (+ 1 (or (xh) 0)))))) (print-histo (histo h.Values))) 
  11 2377 43 2378 90 2379 114 2380 138 2381 133 2382 132 2383 143 2384 127 2385 147 2386 130 2387 136 2388 145 2389 223 2390 430 2391 354 2392 177 2393 72 2394 

两个钟形曲线,一个宽,一个窄。

 (let (h (Hashtable.)) (dotimes i 6553600 (let (r (Random. i)) ; using sequential seed (dotimes j 2 r.Next) ; skipping 2 results (let (x (r.Next (* 15 183))) (xh (+ 1 (or (xh) 0)))))) (print-histo (histo h.Values))) 
  12 2380 104 2381 143 2382 123 2383 106 2384 55 2385 115 2386 387 2387 511 2388 537 2389 454 2390 194 2391 4 2392 

有效的两个钟形曲线。

 (let (h (Hashtable.)) (dotimes i 6553600 (let (r (Random. i)) ; using sequential seed (dotimes j 3 r.Next) ; skipping 3 results (let (x (r.Next (* 15 183))) (xh (+ 1 (or (xh) 0)))))) (print-histo (histo h.Values))) 
  18 2384 154 2385 432 2386 758 2387 798 2388 477 2389 105 2390 3 2391 

最后一个简单的钟形曲线。

总之,看起来有两个不同的问题:第三个样本一般不是很一致,第一个样本产生的种子要么强调问题要么显示单独的问题。