rosu_pp/util/random/
csharp.rs

1use crate::util::hint::unlikely;
2
3// <https://github.com/dotnet/runtime/blob/5535e31a712343a63f5d7d796cd874e563e5ac14/src/libraries/System.Private.CoreLib/src/System/Random.cs#L13>
4pub struct Random {
5    prng: CompatPrng,
6}
7
8impl Random {
9    // <https://github.com/dotnet/runtime/blob/5535e31a712343a63f5d7d796cd874e563e5ac14/src/libraries/System.Private.CoreLib/src/System/Random.cs#L41>
10    pub fn new(seed: i32) -> Self {
11        Self {
12            // <https://github.com/dotnet/runtime/blob/15872212c29cecc8d82da4548c3060f2614665f7/src/libraries/System.Private.CoreLib/src/System/Random.CompatImpl.cs#L22>
13            prng: CompatPrng::initialize(seed),
14        }
15    }
16
17    // <https://github.com/dotnet/runtime/blob/15872212c29cecc8d82da4548c3060f2614665f7/src/libraries/System.Private.CoreLib/src/System/Random.CompatImpl.cs#L26>
18    pub const fn next(&mut self) -> i32 {
19        self.prng.internal_sample()
20    }
21
22    // <https://github.com/dotnet/runtime/blob/15872212c29cecc8d82da4548c3060f2614665f7/src/libraries/System.Private.CoreLib/src/System/Random.CompatImpl.cs#L28>
23    pub fn next_max(&mut self, max: i32) -> i32 {
24        (self.prng.sample() * f64::from(max)) as i32
25    }
26}
27
28// <https://github.com/dotnet/runtime/blob/15872212c29cecc8d82da4548c3060f2614665f7/src/libraries/System.Private.CoreLib/src/System/Random.CompatImpl.cs#L256>
29struct CompatPrng {
30    seed_array: [i32; 56],
31    inext: i32,
32    inextp: i32,
33}
34
35impl CompatPrng {
36    fn initialize(seed: i32) -> Self {
37        let mut seed_array = [0; 56];
38
39        let subtraction = if unlikely(seed == i32::MIN) {
40            i32::MAX
41        } else {
42            i32::abs(seed)
43        };
44
45        let mut mj = 161_803_398 - subtraction; // * magic number based on Phi (golden ratio)
46        seed_array[55] = mj;
47        let mut mk = 1;
48        let mut ii = 0;
49
50        for _ in 1..55 {
51            // * The range [1..55] is special (Knuth) and so we're wasting the 0'th position.
52            ii += 21;
53
54            if ii >= 55 {
55                ii -= 55;
56            }
57
58            seed_array[ii] = mk;
59            mk = mj - mk;
60            if mk < 0 {
61                mk += i32::MAX;
62            }
63
64            mj = seed_array[ii];
65        }
66
67        for _ in 1..5 {
68            for i in 1..56 {
69                let mut n = i + 30;
70
71                if n >= 55 {
72                    n -= 55;
73                }
74
75                seed_array[i] = seed_array[i].wrapping_sub(seed_array[1 + n]);
76
77                if seed_array[i] < 0 {
78                    seed_array[i] += i32::MAX;
79                }
80            }
81        }
82
83        Self {
84            seed_array,
85            inext: 0,
86            inextp: 21,
87        }
88    }
89
90    fn sample(&mut self) -> f64 {
91        f64::from(self.internal_sample()) * (1.0 / f64::from(i32::MAX))
92    }
93
94    const fn internal_sample(&mut self) -> i32 {
95        let mut loc_inext = self.inext;
96        loc_inext += 1;
97
98        if loc_inext >= 56 {
99            loc_inext = 1;
100        }
101
102        let mut loc_inextp = self.inextp;
103        loc_inextp += 1;
104
105        if loc_inextp >= 56 {
106            loc_inextp = 1;
107        }
108
109        let mut ret_val =
110            self.seed_array[loc_inext as usize] - self.seed_array[loc_inextp as usize];
111
112        if ret_val == i32::MAX {
113            ret_val -= 1;
114        }
115
116        if ret_val < 0 {
117            ret_val += i32::MAX;
118        }
119
120        self.seed_array[loc_inext as usize] = ret_val;
121        self.inext = loc_inext;
122        self.inextp = loc_inextp;
123
124        ret_val
125    }
126}