devela/num/rand/xorshift/
u128p.rs

1// devela::num::rand::xorshift::u128
2//
3//! 128-bit + version of XorShift.
4//
5
6#[cfg(any(feature = "join", feature = "split"))]
7use crate::Cast;
8use crate::{ConstDefault, Own};
9
10/// The `XorShift128+` <abbr title="Pseudo-Random Number Generator">PRNG</abbr>.
11///
12/// It has a 128-bit state and generates 64-bit numbers.
13///
14/// It offers a good balance between quality, speed and state size, and
15/// has statistical properties than [`XorShift128`][crate::XorShift128].
16#[must_use]
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub struct XorShift128p([u64; 2]);
19
20/// Creates a new PRNG initialized with the default fixed seed.
21impl Default for XorShift128p {
22    fn default() -> Self {
23        Self::DEFAULT
24    }
25}
26/// Creates a new PRNG initialized with the default fixed seed.
27impl ConstDefault for XorShift128p {
28    const DEFAULT: Self = Self::new_unchecked(Self::DEFAULT_SEED);
29}
30
31// private associated items
32impl XorShift128p {
33    const DEFAULT_SEED: [u64; 2] = [0xDEFA_0017_DEFA_0017; 2];
34
35    #[cold] #[allow(dead_code)] #[rustfmt::skip]
36    const fn cold_path_default() -> Self { Self::new_unchecked(Self::DEFAULT_SEED) }
37}
38
39impl XorShift128p {
40    /// Returns a seeded `XorShift128+` generator from the given 2 × 64-bit seeds.
41    ///
42    /// If the seed is `0`, the default seed is used instead.
43    pub const fn new(seeds: [u64; 2]) -> Self {
44        if (seeds[0] | seeds[1]) == 0 {
45            Self::cold_path_default()
46        } else {
47            Self(seeds)
48        }
49    }
50
51    /// Returns a seeded `XorShift128p` generator from the given 8-bit seed,
52    /// unchecked.
53    ///
54    /// The seeds must not be all `0`, otherwise every result will also be `0`.
55    ///
56    /// # Panics
57    /// Panics in debug if the seeds are all `0`.
58    pub const fn new_unchecked(seeds: [u64; 2]) -> Self {
59        debug_assert![(seeds[0] | seeds[1]) != 0, "Seeds must be non-zero"];
60        Self(seeds)
61    }
62
63    #[must_use]
64    /// Returns the PRNG's inner state as a raw snapshot.
65    pub const fn inner_state(self) -> [u64; 2] {
66        self.0
67    }
68    /// Restores the PRNG from the given state.
69    pub const fn from_state(state: [u64; 2]) -> Self {
70        Self(state)
71    }
72
73    /// Returns the current random `u64`.
74    #[must_use]
75    pub const fn current_u64(&self) -> u64 {
76        self.0[0].wrapping_add(self.0[1])
77    }
78
79    /// Returns the next random `u64`.
80    #[must_use]
81    pub fn next_u64(&mut self) -> u64 {
82        let [s0, mut s1] = [self.0[0], self.0[1]];
83        let result = s0.wrapping_add(s1);
84
85        s1 ^= s0;
86        self.0[0] = s0.rotate_left(55) ^ s1 ^ (s1 << 14); // a, b
87        self.0[1] = s1.rotate_left(36); // c
88
89        result
90    }
91
92    /// Returns a copy of the next new random state.
93    pub const fn peek_next_state(&self) -> Self {
94        let mut x = self.0;
95        let [s0, mut s1] = [x[0], x[1]];
96
97        s1 ^= s0;
98        x[0] = s0.rotate_left(55) ^ s1 ^ (s1 << 14); // a, b
99        x[1] = s1.rotate_left(36); // c
100
101        Self(x)
102    }
103
104    /// Returns both the next random state and the `u64` value.
105    pub const fn own_next_u64(self) -> Own<Self, u64> {
106        let s = self.peek_next_state();
107        let v = s.current_u64();
108        Own::new(s, v)
109    }
110}
111
112/// # Extra constructors
113impl XorShift128p {
114    /// Returns a seeded `XorShift128+` generator from the given 128-bit seed.
115    ///
116    /// The seeds will be split in little endian order.
117    #[cfg(feature = "split")]
118    #[cfg_attr(feature = "nightly_doc", doc(cfg(feature = "split")))]
119    pub const fn new1_u128(seed: u128) -> Self {
120        Self::new(Cast(seed).into_u64_le())
121    }
122
123    /// Returns a seeded `XorShift128+` generator from the given 2 × 64-bit seeds.
124    ///
125    /// This is an alias of [`new`][Self#method.new].
126    pub const fn new2_u64(seeds: [u64; 2]) -> Self {
127        Self::new(seeds)
128    }
129
130    /// Returns a seeded `XorShift128+` generator from the given 4 × 32-bit seeds.
131    ///
132    /// The seeds will be joined in little endian order.
133    #[cfg(feature = "join")]
134    #[cfg_attr(feature = "nightly_doc", doc(cfg(feature = "join")))]
135    pub const fn new4_u32(seeds: [u32; 4]) -> Self {
136        Self::new([
137            Cast::<u64>::from_u32_le([seeds[0], seeds[1]]),
138            Cast::<u64>::from_u32_le([seeds[2], seeds[3]]),
139        ])
140    }
141
142    /// Returns a seeded `XorShift128+` generator from the given 8 × 16-bit seeds.
143    ///
144    /// The seeds will be joined in little endian order.
145    #[cfg(feature = "join")]
146    #[cfg_attr(feature = "nightly_doc", doc(cfg(feature = "join")))]
147    pub const fn new8_u16(seeds: [u16; 8]) -> Self {
148        Self::new([
149            Cast::<u64>::from_u16_le([seeds[0], seeds[1], seeds[2], seeds[3]]),
150            Cast::<u64>::from_u16_le([seeds[4], seeds[5], seeds[6], seeds[7]]),
151        ])
152    }
153
154    /// Returns a seeded `XorShift128` generator from the given 4 × 8-bit seeds.
155    ///
156    /// The seeds will be joined in little endian order.
157    pub const fn new16_u8(seeds: [u8; 16]) -> Self {
158        let s = seeds;
159        Self::new([
160            u64::from_le_bytes([s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]]),
161            u64::from_le_bytes([s[8], s[9], s[10], s[11], s[12], s[13], s[14], s[15]]),
162        ])
163    }
164}
165
166#[cfg(feature = "dep_rand_core")]
167#[cfg_attr(feature = "nightly_doc", doc(cfg(feature = "dep_rand_core")))]
168mod impl_rand {
169    use super::XorShift128p;
170    use crate::_dep::rand_core::{RngCore, SeedableRng};
171
172    impl RngCore for XorShift128p {
173        /// Returns the next random `u32`,
174        /// from the first 32-bits of `next_u64`.
175        fn next_u32(&mut self) -> u32 {
176            self.next_u64() as u32
177        }
178
179        /// Returns the next random `u64`.
180        fn next_u64(&mut self) -> u64 {
181            self.next_u64()
182        }
183
184        fn fill_bytes(&mut self, dest: &mut [u8]) {
185            let mut i = 0;
186            while i < dest.len() {
187                let random_u64 = self.next_u64();
188                let bytes = random_u64.to_le_bytes();
189                let remaining = dest.len() - i;
190
191                if remaining >= 8 {
192                    dest[i..i + 8].copy_from_slice(&bytes);
193                    i += 8;
194                } else {
195                    dest[i..].copy_from_slice(&bytes[..remaining]);
196                    break;
197                }
198            }
199        }
200    }
201
202    impl SeedableRng for XorShift128p {
203        type Seed = [u8; 16];
204
205        /// When seeded with zero this implementation uses the default seed
206        /// value as the cold path.
207        fn from_seed(seed: Self::Seed) -> Self {
208            let mut seed_u64s = [0u64; 2];
209            if seed == [0; 16] {
210                Self::cold_path_default()
211            } else {
212                for i in 0..2 {
213                    seed_u64s[i] = u64::from_le_bytes([
214                        seed[i * 8],
215                        seed[i * 8 + 1],
216                        seed[i * 8 + 2],
217                        seed[i * 8 + 3],
218                        seed[i * 8 + 4],
219                        seed[i * 8 + 5],
220                        seed[i * 8 + 6],
221                        seed[i * 8 + 7],
222                    ]);
223                }
224                Self::new_unchecked(seed_u64s)
225            }
226        }
227    }
228}