Skip to main content

rand/rngs/
xoshiro256plusplus.rs

1// Copyright 2018 Developers of the Rand project.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use core::convert::Infallible;
10use rand_core::{SeedableRng, TryRng, utils};
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Serialize};
13
14/// A xoshiro256++ random number generator.
15///
16/// The xoshiro256++ algorithm is not suitable for cryptographic purposes, but
17/// is very fast and has excellent statistical properties.
18///
19/// The algorithm used here is translated from [the `xoshiro256plusplus.c`
20/// reference source code](http://xoshiro.di.unimi.it/xoshiro256plusplus.c) by
21/// David Blackman and Sebastiano Vigna.
22#[derive(Debug, Clone, PartialEq, Eq)]
23#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
24pub struct Xoshiro256PlusPlus {
25    s: [u64; 4],
26}
27
28impl SeedableRng for Xoshiro256PlusPlus {
29    type Seed = [u8; 32];
30
31    /// Create a new `Xoshiro256PlusPlus`.  If `seed` is entirely 0, it will be
32    /// mapped to a different seed.
33    #[inline]
34    fn from_seed(seed: [u8; 32]) -> Xoshiro256PlusPlus {
35        let state = utils::read_words(&seed);
36        // Check for zero on aligned integers for better code generation.
37        // Furtermore, seed_from_u64(0) will expand to a constant when optimized.
38        if state.iter().all(|&x| x == 0) {
39            return Self::seed_from_u64(0);
40        }
41        Xoshiro256PlusPlus { s: state }
42    }
43
44    /// Create a new `Xoshiro256PlusPlus` from a `u64` seed.
45    ///
46    /// This uses the SplitMix64 generator internally.
47    #[inline]
48    fn seed_from_u64(mut state: u64) -> Self {
49        const PHI: u64 = 0x9e3779b97f4a7c15;
50        let mut s = [0; 4];
51        for i in s.iter_mut() {
52            state = state.wrapping_add(PHI);
53            let mut z = state;
54            z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
55            z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
56            z = z ^ (z >> 31);
57            *i = z;
58        }
59        // By using a non-zero PHI we are guaranteed to generate a non-zero state
60        // Thus preventing a recursion between from_seed and seed_from_u64.
61        debug_assert_ne!(s, [0; 4]);
62        Xoshiro256PlusPlus { s }
63    }
64}
65
66impl TryRng for Xoshiro256PlusPlus {
67    type Error = Infallible;
68
69    #[inline]
70    fn try_next_u32(&mut self) -> Result<u32, Infallible> {
71        // The lowest bits have some linear dependencies, so we use the
72        // upper bits instead.
73        self.try_next_u64().map(|val| (val >> 32) as u32)
74    }
75
76    #[inline]
77    fn try_next_u64(&mut self) -> Result<u64, Infallible> {
78        let res = self.s[0]
79            .wrapping_add(self.s[3])
80            .rotate_left(23)
81            .wrapping_add(self.s[0]);
82
83        let t = self.s[1] << 17;
84
85        self.s[2] ^= self.s[0];
86        self.s[3] ^= self.s[1];
87        self.s[1] ^= self.s[2];
88        self.s[0] ^= self.s[3];
89
90        self.s[2] ^= t;
91
92        self.s[3] = self.s[3].rotate_left(45);
93
94        Ok(res)
95    }
96
97    #[inline]
98    fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Infallible> {
99        utils::fill_bytes_via_next_word(dst, || self.try_next_u64())
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::Xoshiro256PlusPlus;
106    use rand_core::{Rng, SeedableRng};
107
108    #[test]
109    fn reference() {
110        let mut rng = Xoshiro256PlusPlus::from_seed([
111            1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0,
112            0, 0, 0,
113        ]);
114        // These values were produced with the reference implementation:
115        // http://xoshiro.di.unimi.it/xoshiro256plusplus.c
116        let expected = [
117            41943041,
118            58720359,
119            3588806011781223,
120            3591011842654386,
121            9228616714210784205,
122            9973669472204895162,
123            14011001112246962877,
124            12406186145184390807,
125            15849039046786891736,
126            10450023813501588000,
127        ];
128        for &e in &expected {
129            assert_eq!(rng.next_u64(), e);
130        }
131    }
132
133    #[test]
134    fn stable_seed_from_u64_and_from_seed() {
135        // We don't guarantee value-stability for SmallRng but this
136        // could influence keeping stability whenever possible (e.g. after optimizations).
137        let mut rng = Xoshiro256PlusPlus::seed_from_u64(0);
138        // from_seed([0; 32]) should produce the same state as seed_from_u64(0).
139        let mut rng_from_seed_0 = Xoshiro256PlusPlus::from_seed([0; 32]);
140        let expected = [
141            5987356902031041503,
142            7051070477665621255,
143            6633766593972829180,
144            211316841551650330,
145            9136120204379184874,
146            379361710973160858,
147            15813423377499357806,
148            15596884590815070553,
149            5439680534584881407,
150            1369371744833522710,
151        ];
152        for &e in &expected {
153            assert_eq!(rng.next_u64(), e);
154            assert_eq!(rng_from_seed_0.next_u64(), e);
155        }
156    }
157}