dotnet35_rand_rs/
lib.rs

1//! 一个模拟 .NET 3.5 的 Random 类的库
2//!
3//! A library for simulating the .NET 3.5 Random class
4//!
5//! 用法 Usage:
6//! ```rust
7//! use dotnet35_rand_rs::DotNet35Random;
8//!
9//! let mut rand = DotNet35Random::new(0);
10//! println!("{}", rand.next());
11//!
12//! ```
13//!
14//! by shenjackyuanjie
15
16use std::default::Default;
17use std::time::{SystemTime, UNIX_EPOCH};
18
19/// .NET 3.5 的 Random 类的常量
20/// Constants of .NET 3.5 Random class
21///
22/// "万一你真需要修改常量呢?"
23///
24/// "What if you really need to modify the constants?"
25#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
26pub struct DotNet35Const {
27    pub mz: i32,
28    pub mbig: i32,
29    pub mseed: i32,
30}
31
32impl DotNet35Const {
33    /// 默认的种子
34    pub const DEFAULT_MSEED: i32 = 161803398;
35
36    /// "万一你真需要修改常量呢?"
37    ///
38    /// "What if you really need to modify the constants?"
39    pub const fn new(mz: i32, mbig: i32, mseed: i32) -> Self {
40        Self { mz, mbig, mseed }
41    }
42
43    pub const fn new_default() -> Self {
44        Self {
45            mz: 0,
46            mbig: i32::MAX,
47            mseed: Self::DEFAULT_MSEED,
48        }
49    }
50}
51
52impl Default for DotNet35Const {
53    /// 默认值
54    ///
55    /// default value
56    fn default() -> Self {
57        Self::new_default()
58    }
59}
60
61/// 用于模拟 .NET 3.5 的 Random 类
62/// A struct for simulating the .NET 3.5 Random class
63///
64/// 所有的方法都是公开的, 以防万一 (点名批评某个模拟Java random的库)
65///
66/// All methods are public, just in case you need it
67///
68/// 用法 Usage:
69/// ```rust
70/// use dotnet35_rand_rs::DotNet35Random;
71///
72/// let mut rand = DotNet35Random::new(0);
73/// println!("{}", rand.next());
74/// ```
75///
76/// 参考源码 Reference source code:
77/// 感谢 @DoubleUTH 提供的微软源码的链接
78/// Thanks to @DoubleUTH for providing the link to the Microsoft source code
79///
80/// 请注意, 这里的源码进行了二次格式化, 看着好看一些, 其余没有修改
81/// Notice: The source code here has been formatted twice to make it look better, and the rest has not been modified
82/// https://github.com/microsoft/referencesource/blob/51cf7850defa8a17d815b4700b67116e3fa283c2/mscorlib/system/random.cs
83/// ```csharp
84/// // ==++==
85/// //
86/// //   Copyright (c) Microsoft Corporation.  All rights reserved.
87/// //
88/// // ==--==
89/// /*============================================================
90/// **
91/// ** Class:  Random
92/// **
93/// **
94/// ** Purpose: A random number generator.
95/// **
96/// **
97/// ===========================================================*/
98/// namespace System
99/// {
100///     using System;
101///     using System.Runtime;
102///     using System.Runtime.CompilerServices;
103///     using System.Globalization;
104///     using System.Diagnostics.Contracts;
105///     [System.Runtime.InteropServices.ComVisible(true)]
106///     [Serializable]
107///     public class Random
108///     {
109///         //
110///         // Private Constants
111///         //
112///         private const int MBIG = Int32.MaxValue;
113///         private const int MSEED = 161803398;
114///         private const int MZ = 0;
115///         //
116///         // Member Variables
117///         //
118///         private int inext;
119///         private int inextp;
120///         private int[] SeedArray = new int[56];
121///         //
122///         // Constructors
123///         //
124///
125///         public Random()
126///           : this(Environment.TickCount)
127///         {
128///         }
129///         public Random(int Seed)
130///         {
131///             int ii;
132///             int mj, mk;
133///
134///             //Initialize our Seed array.
135///             //This algorithm comes from Numerical Recipes in C (2nd Ed.)
136///             int subtraction = (Seed == Int32.MinValue) ? Int32.MaxValue : Math.Abs(Seed);
137///             mj = MSEED - subtraction;
138///             SeedArray[55] = mj;
139///             mk = 1;
140///             for (int i = 1; i < 55; i++)
141///             {  //Apparently the range [1..55] is special (Knuth) and so we're wasting the 0'th position.
142///                 ii = (21 * i) % 55;
143///                 SeedArray[ii] = mk;
144///                 mk = mj - mk;
145///                 if (mk < 0) mk += MBIG;
146///                 mj = SeedArray[ii];
147///             }
148///             for (int k = 1; k < 5; k++)
149///             {
150///                 for (int i = 1; i < 56; i++)
151///                 {
152///                     SeedArray[i] -= SeedArray[1 + (i + 30) % 55];
153///                     if (SeedArray[i] < 0) SeedArray[i] += MBIG;
154///                 }
155///             }
156///             inext = 0;
157///             inextp = 21;
158///             Seed = 1;
159///         }
160///         //
161///         // Package Private Methods
162///         //
163///         /*====================================Sample====================================
164///         **Action: Return a new random number [0..1) and reSeed the Seed array.
165///         **Returns: A double [0..1)
166///         **Arguments: None
167///         **Exceptions: None
168///         ==============================================================================*/
169///         protected virtual double Sample()
170///         {
171///             //Including this division at the end gives us significantly improved
172///             //random number distribution.
173///             return (InternalSample() * (1.0 / MBIG));
174///         }
175///         private int InternalSample()
176///         {
177///             int retVal;
178///             int locINext = inext;
179///             int locINextp = inextp;
180///
181///             if (++locINext >= 56) locINext = 1;
182///             if (++locINextp >= 56) locINextp = 1;
183///
184///             retVal = SeedArray[locINext] - SeedArray[locINextp];
185///
186///             if (retVal == MBIG) retVal--;
187///             if (retVal < 0) retVal += MBIG;
188///
189///             SeedArray[locINext] = retVal;
190///
191///             inext = locINext;
192///             inextp = locINextp;
193///
194///             return retVal;
195///         }
196///         //
197///         // Public Instance Methods
198///         //
199///         /*=====================================Next=====================================
200///         **Returns: An int [0..Int32.MaxValue)
201///         **Arguments: None
202///         **Exceptions: None.
203///         ==============================================================================*/
204///         public virtual int Next()
205///         {
206///             return InternalSample();
207///         }
208///         private double GetSampleForLargeRange()
209///         {
210///             // The distribution of double value returned by Sample
211///             // is not distributed well enough for a large range.
212///             // If we use Sample for a range [Int32.MinValue..Int32.MaxValue)
213///             // We will end up getting even numbers only.
214///
215///             int result = InternalSample();
216///             // Note we can't use addition here. The distribution will be bad if we do that.
217///             bool negative = (InternalSample() % 2 == 0) ? true : false;  // decide the sign based on second sample
218///             if (negative)
219///             {
220///                 result = -result;
221///             }
222///             double d = result;
223///             d += (Int32.MaxValue - 1); // get a number in range [0 .. 2 * Int32MaxValue - 1)
224///             d /= 2 * (uint)Int32.MaxValue - 1;
225///             return d;
226///         }
227///         /*=====================================Next=====================================
228///         **Returns: An int [minvalue..maxvalue)
229///         **Arguments: minValue -- the least legal value for the Random number.
230///         **           maxValue -- One greater than the greatest legal return value.
231///         **Exceptions: None.
232///         ==============================================================================*/
233///         public virtual int Next(int minValue, int maxValue)
234///         {
235///             if (minValue > maxValue)
236///             {
237///                 throw new ArgumentOutOfRangeException("minValue", Environment.GetResourceString("Argument_MinMaxValue", "minValue", "maxValue"));
238///             }
239///             Contract.EndContractBlock();
240///
241///             long range = (long)maxValue - minValue;
242///             if (range <= (long)Int32.MaxValue)
243///             {
244///                 return ((int)(Sample() * range) + minValue);
245///             }
246///             else
247///             {
248///                 return (int)((long)(GetSampleForLargeRange() * range) + minValue);
249///             }
250///         }
251///         /*=====================================Next=====================================
252///         **Returns: An int [0..maxValue)
253///         **Arguments: maxValue -- One more than the greatest legal return value.
254///         **Exceptions: None.
255///         ==============================================================================*/
256///         public virtual int Next(int maxValue)
257///         {
258///             if (maxValue < 0)
259///             {
260///                 throw new ArgumentOutOfRangeException("maxValue", Environment.GetResourceString("ArgumentOutOfRange_MustBePositive", "maxValue"));
261///             }
262///             Contract.EndContractBlock();
263///             return (int)(Sample() * maxValue);
264///         }
265///         /*=====================================Next=====================================
266///         **Returns: A double [0..1)
267///         **Arguments: None
268///         **Exceptions: None
269///         ==============================================================================*/
270///         public virtual double NextDouble()
271///         {
272///             return Sample();
273///         }
274///         /*==================================NextBytes===================================
275///         **Action:  Fills the byte array with random bytes [0..0x7f].  The entire array is filled.
276///         **Returns:Void
277///         **Arugments:  buffer -- the array to be filled.
278///         **Exceptions: None
279///         ==============================================================================*/
280///         public virtual void NextBytes(byte[] buffer)
281///         {
282///             if (buffer == null) throw new ArgumentNullException("buffer");
283///             Contract.EndContractBlock();
284///             for (int i = 0; i < buffer.Length; i++)
285///             {
286///                 buffer[i] = (byte)(InternalSample() % (Byte.MaxValue + 1));
287///             }
288///         }
289///     }
290/// }
291/// ```
292#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
293pub struct DotNet35Random {
294    /// private int inext;
295    pub inext: usize,
296    /// private int inextp;
297    pub inextp: usize,
298    /// private int[] SeedArray = new int[56];
299    pub seed_array: [i32; 56],
300    /// consts
301    pub consts: DotNet35Const,
302    /// seed
303    pub seed: i32,
304}
305
306impl DotNet35Random {
307    /// 默认的构造函数
308    ///
309    /// default constructor
310    pub fn new(seed: i32) -> Self {
311        let consts = DotNet35Const::default();
312        DotNet35Random::new_with_const(seed, consts)
313    }
314
315    /// `public DotNet35Random() : this(Environment.TickCount)`
316    ///
317    /// 用当前时间作为种子的构造函数
318    /// Constructor with current time as seed
319    pub fn new_now() -> Self {
320        let consts = DotNet35Const::default();
321        // Environment.TickCount
322
323        let start = SystemTime::now();
324        let since_the_epoch = start
325            .duration_since(UNIX_EPOCH)
326            .unwrap_or(std::time::Duration::new(0, 0));
327        let tick_count = since_the_epoch.as_millis() as i32;
328        DotNet35Random::new_with_const(tick_count, consts)
329    }
330
331    /// "万一你真需要修改常量呢?"
332    ///
333    /// "What if you really need to modify the constants?"
334    pub fn new_with_const(seed: i32, consts: DotNet35Const) -> Self {
335        let mut seed_array = [0; 56];
336        let subtraction = if seed == i32::MIN {
337            i32::MAX
338        } else {
339            seed.abs()
340        };
341        let mut mj: i32 = consts.mseed - subtraction;
342        seed_array[55] = mj;
343        let mut mk = 1;
344
345        for i in 1..55 {
346            let ii = 21 * i % 55;
347            seed_array[ii] = mk;
348
349            // overflow warning! use wrapping_sub
350            mk = mj.wrapping_sub(mk);
351            if mk < 0 {
352                // 可能导致算数溢出, 使用 wrapping_add
353                mk = mk.wrapping_add(consts.mbig);
354            }
355            mj = seed_array[ii];
356        }
357        for _ in 1..5 {
358            for i in 1..56 {
359                // 可能导致算数溢出, 使用 wrapping_sub 和 wrapping_add
360                seed_array[i] = seed_array[i].wrapping_sub(seed_array[1 + (i + 30) % 55]);
361                if seed_array[i] < 0 {
362                    seed_array[i] = seed_array[i].wrapping_add(consts.mbig);
363                }
364            }
365        }
366        Self {
367            inext: 0,
368            inextp: 31,
369            seed_array,
370            consts,
371            seed,
372        }
373    }
374
375    /// `private double GetSampleForLargeRange()`
376    ///
377    /// 原始的 GetSampleForLargeRange 方法
378    /// original GetSampleForLargeRange method
379    ///
380    /// 源码注释翻译:
381    /// Sample 返回的 double 值的分布对于大范围来说不够好
382    /// 如果我们使用 Sample 来生成 [Int32.MinValue..Int32.MaxValue) 的范围
383    /// 我们将只会得到偶数
384    ///
385    /// Source code comment:
386    /// The distribution of double value returned by Sample
387    /// is not distributed well enough for a large range.
388    /// if we use Sample for a range [Int32.MinValue..Int32.MaxValue)
389    /// we will end up getting even numbers only.
390    pub fn get_sample_for_large_range(&mut self) -> f64 {
391        let result = self.internal_sample();
392        // 注意, 我们不能使用加法. 如果我们这么做, 分布将会很糟糕
393        let negative = self.internal_sample() % 2 == 0;
394        // 根据第二个样本决定符号
395        let result = if negative { -result } else { result };
396        let mut d = result as f64;
397        d += (i32::MAX - 1) as f64;
398        // 获取一个范围在 [0 .. 2 * Int32MaxValue - 1) 的数字
399        // 我专门去查了一下 C# 的运算符优先级来写的这一行代码
400        // I specially checked the operator priority of C# to write this line of code
401        d /= ((i32::MAX as u32 * 2) - 1) as f64;
402        d
403    }
404
405    /// `protected virtual double Sample()`
406    ///
407    /// 原始的 Sample 方法
408    /// original Sample method
409    pub fn sample(&mut self) -> f64 {
410        self.internal_sample() as f64 * (1.0 / self.consts.mbig as f64)
411    }
412
413    /// `private int InternalSample()`
414    ///
415    /// 原始的 InternalSample 方法
416    /// original InternalSample method
417    pub fn internal_sample(&mut self) -> i32 {
418        self.inext += 1;
419        if self.inext >= 56 {
420            self.inext = 1;
421        }
422        self.inextp += 1;
423        if self.inextp >= 56 {
424            self.inextp = 1;
425        }
426        let mut ret_val = self.seed_array[self.inext] - self.seed_array[self.inextp];
427        if ret_val == self.consts.mbig {
428            ret_val -= 1;
429        }
430        if ret_val < 0 {
431            ret_val += i32::MAX;
432        }
433        self.seed_array[self.inext] = ret_val;
434        ret_val
435    }
436
437    /// `public virtual int Next()`
438    ///
439    /// 默认的 Next 方法
440    /// default Next method
441    #[allow(clippy::should_implement_trait)]
442    pub fn next(&mut self) -> i32 {
443        self.internal_sample()
444    }
445
446    /// `public virtual int Next(int maxValue)`
447    ///
448    /// 限制最大值的 Next 方法
449    /// Next method with max value
450    pub fn next_with_max(&mut self, max_value: i32) -> Option<i32> {
451        if max_value < self.consts.mz {
452            None
453        } else {
454            Some((self.sample() * max_value as f64) as i32)
455        }
456    }
457
458    /// `public virtual int Next(int minValue, int maxValue)`
459    ///
460    /// 限制最大值和最小值的 Next 方法
461    /// Next method with max value and min value
462    pub fn next_in_range(&mut self, min_value: i32, max_value: i32) -> Option<i32> {
463        if min_value > max_value {
464            return None;
465        }
466        let range = max_value as i64 - min_value as i64;
467        if range <= i32::MAX as i64 {
468            Some((self.sample() * range as f64) as i32 + min_value)
469        } else {
470            Some((self.get_sample_for_large_range() * range as f64) as i32 + min_value)
471        }
472    }
473
474    /// `public virtual void NextBytes(byte[] buffer)`
475    ///
476    /// 返回随机字节的 NextBytes 方法
477    /// NextBytes method with random bytes
478    pub fn next_bytes(&mut self, buffer: &mut [u8]) {
479        (0..buffer.len()).for_each(|i| {
480            buffer[i] = (self.internal_sample() % u8::MAX as i32) as u8;
481        });
482    }
483
484    /// `public virtual double NextDouble()`
485    ///
486    /// 返回随机浮点数的 NextDouble 方法
487    /// NextDouble method with random double
488    pub fn next_double(&mut self) -> f64 {
489        self.sample()
490    }
491
492    /// 更 rusty 一些的 next_f64 方法
493    /// more rusty next_f64 method
494    pub fn next_f64(&mut self) -> f64 {
495        self.sample()
496    }
497
498    /// 更 rusty 一些的 next_i32 方法
499    /// more rusty next_i32 method
500    pub fn next_i32(&mut self) -> i32 {
501        self.internal_sample()
502    }
503
504    /// 更 rusty 一些的 next_u32 方法
505    /// more rusty next_u32 method
506    pub fn next_u32(&mut self) -> u32 {
507        self.internal_sample() as u32
508    }
509
510    pub fn next_u8_vec(&mut self, len: usize) -> Vec<u8> {
511        let mut vec = Vec::with_capacity(len);
512        for _ in 0..len {
513            vec.push((self.internal_sample() % u8::MAX as i32) as u8);
514        }
515        vec
516    }
517}
518
519#[test]
520fn create() {
521    let mut rand = DotNet35Random::new(0);
522    assert_eq!(rand.next(), 1976681210);
523    assert_eq!(rand.next(), 551155468);
524}
525
526#[test]
527fn verify() {
528    let mut rand = DotNet35Random::new(1919810);
529    assert_eq!(rand.next(), 429045588);
530    assert_eq!(rand.next(), 1732108734);
531    assert_eq!(rand.next(), 970222201);
532    assert_eq!(rand.next(), 369077841);
533    assert_eq!(rand.next_double(), 0.11859490215712921);
534    assert_eq!(rand.next(), 298877408);
535}
536
537#[test]
538fn create_now() {
539    let mut rand = DotNet35Random::new_now();
540    for _ in 0..100 {
541        assert!(rand.next_with_max(1000).unwrap() < 1000);
542        let next = rand.next_in_range(1000, 2000).unwrap();
543        assert!((1000..2000).contains(&next));
544    }
545}