@@ -41,6 +41,37 @@ pub struct SplitMix64 {
4141 gamma : u64 ,
4242}
4343
44+ /// Random Number Generator using Xoshiro256++.
45+ ///
46+ /// This is the state of the Xoshiro256++ pseudo random number generator. It
47+ /// uses 256-bit of state and generates 64-bit random numbers. This PRNG is
48+ /// **not** cryptgraphically secure, but is otherwise a good fit for nearly
49+ /// all purposes.
50+ ///
51+ /// This implements `Clone` and `Copy` for verbatim copies. Use
52+ /// [`jump128()`](Self::jump128) to produce non-verbatim copies with better
53+ /// random distribution.
54+ #[ derive( Clone , Debug , Eq , PartialEq ) ]
55+ pub struct Xoshiro256pp {
56+ state : [ u64 ; 4 ] ,
57+ }
58+
59+ // Static calculation of the first few values from Mix64(0). The array is
60+ // statically verified to contain no 0 values (which sufficiently resolves
61+ // non-0 seeding requirements of some RNGs).
62+ const MIX64_0 : [ u64 ; 4 ] = {
63+ let mut mix = Mix64 :: with_seed ( 0 ) ;
64+ let v = [ mix. next64 ( ) ; 4 ] ;
65+
66+ let mut i = 0 ;
67+ while i < v. len ( ) {
68+ assert ! ( v[ i] != 0 ) ;
69+ i += 1 ;
70+ }
71+
72+ v
73+ } ;
74+
4475impl Mix64 {
4576 const GAMMA : u64 = 0x9e3779b97f4a7c15 ;
4677
@@ -168,6 +199,125 @@ impl From<Mix64> for SplitMix64 {
168199 }
169200}
170201
202+ impl Xoshiro256pp {
203+ const fn combine ( s : & [ u64 ; 4 ] ) -> u64 {
204+ s[ 0 ] . wrapping_add ( s[ 3 ] )
205+ . rotate_left ( 23 )
206+ . wrapping_add ( s[ 0 ] )
207+ }
208+
209+ const fn step ( s : & mut [ u64 ; 4 ] ) {
210+ let t = s[ 1 ] << 17 ;
211+
212+ s[ 2 ] ^= s[ 0 ] ;
213+ s[ 3 ] ^= s[ 1 ] ;
214+ s[ 1 ] ^= s[ 2 ] ;
215+ s[ 0 ] ^= s[ 3 ] ;
216+
217+ s[ 2 ] ^= t;
218+ s[ 3 ] = s[ 3 ] . rotate_left ( 45 ) ;
219+ }
220+
221+ const fn jump ( s : & mut [ u64 ; 4 ] , jump_table : & [ u64 ; 4 ] ) {
222+ let mut t = [ 0 ; 4 ] ;
223+
224+ let mut i = 0 ;
225+ while i < 4 {
226+ let mut j = 0 ;
227+ while j < 64 {
228+ if ( jump_table[ i] & ( 1 << j) ) != 0 {
229+ t[ 0 ] ^= s[ 0 ] ;
230+ t[ 1 ] ^= s[ 1 ] ;
231+ t[ 2 ] ^= s[ 2 ] ;
232+ t[ 3 ] ^= s[ 3 ] ;
233+ }
234+ Self :: step ( s) ;
235+ j += 1 ;
236+ }
237+ i += 1 ;
238+ }
239+
240+ * s = t;
241+ }
242+
243+ // Steps 2^128 times, using pre-calculated jump tables.
244+ const fn step128 ( s : & mut [ u64 ; 4 ] ) {
245+ Self :: jump (
246+ s,
247+ & [
248+ 0x180ec6d33cfd0aba ,
249+ 0xd5a61266f0c9392c ,
250+ 0xa9582618e03fc9aa ,
251+ 0x39abdc4529b1661c ,
252+ ] ,
253+ )
254+ }
255+
256+ // Steps 2^192 times, using pre-calculated jump tables.
257+ const fn step192 ( s : & mut [ u64 ; 4 ] ) {
258+ Self :: jump (
259+ s,
260+ & [
261+ 0x76e15d3efefdcbbf ,
262+ 0xc5004e441c522fb3 ,
263+ 0x77710069854ee241 ,
264+ 0x39109bb02acbe635 ,
265+ ] ,
266+ )
267+ }
268+
269+ /// Create a new instance with the given seed.
270+ ///
271+ /// The seed is used unmodified as the internal state of the Xoshiro256++
272+ /// RNG, and thus will produce the same results as other implementations
273+ /// with this seed (except if the seed is 0, described below).
274+ ///
275+ /// A seed of all 0 is not allowed for Xoshiro256++. This implementation
276+ /// maps a seed of all 0 to `Self::from_splitmix64(0)`.
277+ pub const fn with_seed ( seed : [ u64 ; 4 ] ) -> Self {
278+ if seed[ 0 ] == 0 && seed[ 1 ] == 0 && seed[ 2 ] == 0 && seed[ 3 ] == 0 {
279+ Self {
280+ state : MIX64_0 ,
281+ }
282+ } else {
283+ Self {
284+ state : seed,
285+ }
286+ }
287+ }
288+
289+ /// Create a new instance from a SplitMix64.
290+ ///
291+ /// Use the [`SplitMix64`] instance to seed the 256-bit of state of a new
292+ /// RNG instance.
293+ pub const fn from_splitmix64 ( mix : & mut SplitMix64 ) -> Self {
294+ Self :: with_seed ( [ mix. next64 ( ) ; 4 ] )
295+ }
296+
297+ /// Produce the next 64-bit random number.
298+ pub const fn next64 ( & mut self ) -> u64 {
299+ let r = Self :: combine ( & self . state ) ;
300+ Self :: step ( & mut self . state ) ;
301+ r
302+ }
303+
304+ /// Jump over the next 2^128 random numbers.
305+ pub const fn jump128 ( & mut self ) {
306+ Self :: step128 ( & mut self . state ) ;
307+ }
308+
309+ /// Jump over the next 2^192 random numbers.
310+ pub const fn jump192 ( & mut self ) {
311+ Self :: step192 ( & mut self . state ) ;
312+ }
313+ }
314+
315+ impl From < SplitMix64 > for Xoshiro256pp {
316+ fn from ( mut v : SplitMix64 ) -> Self {
317+ Self :: from_splitmix64 ( & mut v)
318+ }
319+ }
320+
171321#[ cfg( test) ]
172322mod test {
173323 use super :: * ;
@@ -229,4 +379,31 @@ mod test {
229379 assert_eq ! ( rng0. split( ) , rng1. split( ) ) ;
230380 }
231381 }
382+
383+ // Run some known-value-tests on Xoshiro256pp.
384+ #[ test]
385+ fn xoshiro256pp_known ( ) {
386+ {
387+ let mut rng0 = Xoshiro256pp :: with_seed ( [ 1 , 2 , 3 , 4 ] ) ;
388+
389+ assert_eq ! ( rng0. next64( ) , 41943041 ) ;
390+ assert_eq ! ( rng0. next64( ) , 58720359 ) ;
391+ assert_eq ! ( rng0. next64( ) , 3588806011781223 ) ;
392+ assert_eq ! ( rng0. next64( ) , 3591011842654386 ) ;
393+
394+ rng0. jump128 ( ) ;
395+
396+ assert_eq ! ( rng0. next64( ) , 10838999831620499216 ) ;
397+ assert_eq ! ( rng0. next64( ) , 8680420094678800874 ) ;
398+ assert_eq ! ( rng0. next64( ) , 9570055643283944810 ) ;
399+ assert_eq ! ( rng0. next64( ) , 7079802948504130534 ) ;
400+
401+ rng0. jump192 ( ) ;
402+
403+ assert_eq ! ( rng0. next64( ) , 7229965972965062926 ) ;
404+ assert_eq ! ( rng0. next64( ) , 2140690761664815708 ) ;
405+ assert_eq ! ( rng0. next64( ) , 5733913562642225265 ) ;
406+ assert_eq ! ( rng0. next64( ) , 10699737370828579003 ) ;
407+ }
408+ }
232409}
0 commit comments