1pub struct XorShiftRng {
4 state: u64,
5}
6
7impl XorShiftRng {
8 pub fn new(seed: u64) -> Self {
11 Self { state: seed.max(1) }
12 }
13
14 pub fn next_noise_sample(&mut self) -> f64 {
16 self.state ^= self.state << 13;
17 self.state ^= self.state >> 7;
18 self.state ^= self.state << 17;
19
20 ((self.state as f64) / (u64::MAX as f64)) * 2.0 - 1.0
21 }
22}
23
24impl Default for XorShiftRng {
25 fn default() -> Self {
28 Self { state: 1 }
29 }
30}
31
32#[cfg(test)]
33mod tests {
34 use super::*;
35
36 #[test]
37 fn test_output_range() {
38 let mut rng = XorShiftRng::new(12345);
39 for _ in 0..10000 {
40 let sample = rng.next_noise_sample();
41 assert!(
42 sample >= -1.0 && sample <= 1.0,
43 "Sample {} out of [-1, 1] range",
44 sample
45 );
46 }
47 }
48
49 #[test]
50 fn test_deterministic_with_same_seed() {
51 let mut rng1 = XorShiftRng::new(42);
52 let mut rng2 = XorShiftRng::new(42);
53
54 for _ in 0..100 {
55 let s1 = rng1.next_noise_sample();
56 let s2 = rng2.next_noise_sample();
57 assert!((s1 - s2).abs() < 1e-15, "Same seed should produce identical sequences");
58 }
59 }
60
61 #[test]
62 fn test_different_seeds_produce_different_sequences() {
63 let mut rng1 = XorShiftRng::new(1);
64 let mut rng2 = XorShiftRng::new(2);
65
66 let mut all_same = true;
67 for _ in 0..100 {
68 let s1 = rng1.next_noise_sample();
69 let s2 = rng2.next_noise_sample();
70 if (s1 - s2).abs() > 1e-15 {
71 all_same = false;
72 break;
73 }
74 }
75 assert!(!all_same, "Different seeds should produce different sequences");
76 }
77
78 #[test]
79 fn test_zero_seed_handled() {
80 let mut rng = XorShiftRng::new(0);
81 let sample = rng.next_noise_sample();
82 assert!(
83 sample >= -1.0 && sample <= 1.0,
84 "Zero seed should work (converted to 1)"
85 );
86 }
87
88 #[test]
89 fn test_default_produces_valid_output() {
90 let mut rng = XorShiftRng::default();
91 for _ in 0..100 {
92 let sample = rng.next_noise_sample();
93 assert!(sample >= -1.0 && sample <= 1.0);
94 }
95 }
96
97 #[test]
98 fn test_default_matches_seed_one() {
99 let mut rng_default = XorShiftRng::default();
100 let mut rng_one = XorShiftRng::new(1);
101
102 for _ in 0..100 {
103 let s1 = rng_default.next_noise_sample();
104 let s2 = rng_one.next_noise_sample();
105 assert!((s1 - s2).abs() < 1e-15, "Default should match seed=1");
106 }
107 }
108
109 #[test]
110 fn test_not_constant() {
111 let mut rng = XorShiftRng::new(42);
112 let first = rng.next_noise_sample();
113 let mut found_different = false;
114
115 for _ in 0..100 {
116 let sample = rng.next_noise_sample();
117 if (sample - first).abs() > 1e-10 {
118 found_different = true;
119 break;
120 }
121 }
122 assert!(found_different, "RNG should produce varying values");
123 }
124
125 #[test]
126 fn test_distribution_rough_balance() {
127 let mut rng = XorShiftRng::new(54321);
128 let mut positive_count = 0;
129 let mut negative_count = 0;
130 let num_samples = 10000;
131
132 for _ in 0..num_samples {
133 let sample = rng.next_noise_sample();
134 if sample > 0.0 {
135 positive_count += 1;
136 } else if sample < 0.0 {
137 negative_count += 1;
138 }
139 }
140
141 let ratio = positive_count as f64 / negative_count as f64;
142 assert!(
143 ratio > 0.8 && ratio < 1.25,
144 "Distribution should be roughly balanced: pos={}, neg={}, ratio={}",
145 positive_count,
146 negative_count,
147 ratio
148 );
149 }
150}