1 /**
2  *xoshiro generators
3  *Authors: lempiji
4  */
5 module xoshiro.xoshiro;
6 
7 import xoshiro.util;
8 
9 import std.random;
10 
11 ///
12 enum XoshiroOpMode
13 {
14     Plus,
15     PlusPlus,
16     StarStar,
17 }
18 
19 ///
20 struct XoshiroEngine(UIntType, XoshiroOpMode mode)
21 {
22     static assert(is(UIntType == uint) || is(UIntType == ulong));
23 
24     enum isUniformRandom = true;
25     enum min = UIntType(0);
26     enum max = UIntType.max;
27 
28 private:
29     UIntType[4] state;
30 
31     enum shiftSize = UIntType.sizeof == 8 ? 17 : 9;
32     enum rotSize = UIntType.sizeof == 8 ? 45 : 11;
33     enum UIntBits = UIntType.sizeof * 8;
34 
35 public:
36     ///
37     this()()
38     {
39         this.seed = 0;
40     }
41 
42     ///
43     this()(UIntType seed)
44     {
45         this.seed = seed;
46     }
47 
48     ///
49     enum empty = false;
50 
51     ///
52     UIntType front() const pure nothrow @safe @nogc @property
53     {
54         static if (mode == XoshiroOpMode.Plus)
55             return state[0] + state[3];
56         else static if (mode == XoshiroOpMode.PlusPlus)
57             return rotl(state[0] + state[3], 23) + state[0];
58         else static if (mode == XoshiroOpMode.StarStar)
59             return rotl(state[1] * 5, 7) * 9;
60         else
61             static assert(false);
62     }
63 
64     ///
65     void popFront() @safe pure nothrow @nogc
66     {
67         const UIntType t = state[1] << shiftSize;
68 
69         state[2] ^= state[0];
70         state[3] ^= state[1];
71         state[1] ^= state[2];
72         state[0] ^= state[3];
73 
74         state[2] ^= t;
75 
76         state[3] = rotl(state[3], rotSize);
77     }
78 
79     ///
80     typeof(this) save() const @safe pure nothrow @nogc @property
81     {
82         return this;
83     }
84 
85     ///
86     void seed(UIntType seed) @safe pure nothrow @nogc
87     {
88         ulong temp = seed;
89         state[0] = cast(UIntType) splitMix(temp);
90         state[1] = cast(UIntType) splitMix(temp);
91         state[2] = cast(UIntType) splitMix(temp);
92         state[3] = cast(UIntType) splitMix(temp);
93     }
94 
95     ///It is equivalent to 2^N calls to popFront()
96     ///
97     /// N = 128 on Xoshiro256  
98     /// N =  64 on Xoshiro128
99     void jump()
100     {
101         static if (UIntType.sizeof == 8)
102         {
103             // 256
104             enum ulong[] JUMP = [
105                     0x180ec6d33cfd0aba, 0xd5a61266f0c9392c, 0xa9582618e03fc9aa,
106                     0x39abdc4529b1661c
107                 ];
108         }
109         else
110         {
111             // 128
112             enum uint[] JUMP = [
113                     0x8764000b, 0xf542d2d3, 0x6fa035c3, 0x77f2db5b
114                 ];
115         }
116 
117         UIntType s0 = 0;
118         UIntType s1 = 0;
119         UIntType s2 = 0;
120         UIntType s3 = 0;
121         foreach (jump; JUMP)
122         {
123             foreach (b; 0 .. UIntBits)
124             {
125                 if (jump & (UIntType(1) << b))
126                 {
127                     s0 ^= state[0];
128                     s1 ^= state[1];
129                     s2 ^= state[2];
130                     s3 ^= state[3];
131                 }
132                 popFront();
133             }
134         }
135 
136         state[0] = s0;
137         state[1] = s1;
138         state[2] = s2;
139         state[3] = s3;
140     }
141 
142     ///It is equivalent to 2^N calls to popFront()
143     ///
144     /// N = 192 on Xoshiro256  
145     /// N =  96 on Xoshiro128
146     void longJump()
147     {
148         static if (UIntType.sizeof == 8)
149         {
150             // 256
151             enum ulong[] LONG_JUMP = [
152                     0x76e15d3efefdcbbf, 0xc5004e441c522fb3, 0x77710069854ee241,
153                     0x39109bb02acbe635
154                 ];
155         }
156         else
157         {
158             // 128
159             enum uint[] LONG_JUMP = [
160                     0xb523952e, 0x0b6f099f, 0xccf5a0ef, 0x1c580662
161                 ];
162         }
163         UIntType s0 = 0;
164         UIntType s1 = 0;
165         UIntType s2 = 0;
166         UIntType s3 = 0;
167         foreach (jump; LONG_JUMP)
168         {
169             foreach (b; 0 .. UIntBits)
170             {
171                 if (jump & (UIntType(1) << b))
172                 {
173                     s0 ^= state[0];
174                     s1 ^= state[1];
175                     s2 ^= state[2];
176                     s3 ^= state[3];
177                 }
178                 popFront();
179             }
180         }
181         state[0] = s0;
182         state[1] = s1;
183         state[2] = s2;
184         state[3] = s3;
185     }
186 }
187 
188 /**
189  * Xoshiro256+
190  * Period: 2 ^ 256 - 1
191  * Footprint: 32 bytes
192  */
193 alias Xoshiro256Plus = XoshiroEngine!(ulong, XoshiroOpMode.Plus);
194 
195 /// ditto
196 @("Overview Xoshiro256+")
197 unittest
198 {
199     import std.random : uniform01;
200 
201     auto rndGen = Xoshiro256Plus(unpredictableSeed!ulong);
202     auto x = uniform01(rndGen);
203     assert(0 <= x && x <= 1);
204 }
205 
206 /**
207  * Xoshiro256++
208  * Period: 2 ^ 256 - 1
209  * Footprint: 32 bytes
210  */
211 alias Xoshiro256PlusPlus = XoshiroEngine!(ulong, XoshiroOpMode.PlusPlus);
212 
213 /// ditto
214 @("Overview Xoshiro256++")
215 unittest
216 {
217     import std.random : uniform01;
218 
219     auto rndGen = Xoshiro256PlusPlus(unpredictableSeed!ulong);
220     auto x = uniform01(rndGen);
221     assert(0 <= x && x <= 1);
222 }
223 
224 /**
225  * Xoshiro256**
226  * Period: 2 ^ 256 - 1
227  * Footprint: 32 bytes
228  */
229 alias Xoshiro256StarStar = XoshiroEngine!(ulong, XoshiroOpMode.StarStar);
230 
231 /// ditto
232 @("Overview Xoshiro256**")
233 unittest
234 {
235     import std.random : uniform01;
236 
237     auto rndGen = Xoshiro256StarStar(unpredictableSeed!ulong);
238     auto x = uniform01(rndGen);
239     assert(0 <= x && x <= 1);
240 }
241 
242 /**
243  * Xoshiro128+
244  * Period: 2 ^ 128 - 1
245  * Footprint: 16 bytes
246  */
247 alias Xoshiro128Plus = XoshiroEngine!(uint, XoshiroOpMode.Plus);
248 
249 /// ditto
250 @("Overview Xoshiro128+")
251 unittest
252 {
253     import std.random : uniform01;
254 
255     auto rndGen = Xoshiro128Plus(unpredictableSeed);
256     auto x = uniform01(rndGen);
257     assert(0 <= x && x <= 1);
258 }
259 
260 /**
261  * Xoshiro128++
262  * Period: 2 ^ 128 - 1
263  * Footprint: 16 bytes
264  */
265 alias Xoshiro128PlusPlus = XoshiroEngine!(uint, XoshiroOpMode.PlusPlus);
266 
267 /// ditto
268 @("Overview Xoshiro128++")
269 unittest
270 {
271     import std.random : uniform01;
272 
273     auto rndGen = Xoshiro128PlusPlus(unpredictableSeed);
274     auto x = uniform01(rndGen);
275     assert(0 <= x && x <= 1);
276 }
277 
278 /**
279  * Xoshiro128**
280  * Period: 2 ^ 128 - 1
281  * Footprint: 16 bytes
282  */
283 alias Xoshiro128StarStar = XoshiroEngine!(uint, XoshiroOpMode.StarStar);
284 
285 /// ditto
286 @("Overview Xoshiro128**")
287 unittest
288 {
289     import std.random : uniform01;
290 
291     auto rndGen = Xoshiro128StarStar(unpredictableSeed);
292     auto x = uniform01(rndGen);
293     assert(0 <= x && x <= 1);
294 }
295 
296 @("isInfinityForwardRange && isUniformRNG && isSeedable")
297 unittest
298 {
299     import std.range;
300     import std.meta : AliasSeq;
301 
302     alias Ts = AliasSeq!(
303         Xoshiro128Plus, Xoshiro128PlusPlus, Xoshiro128StarStar,
304         Xoshiro256Plus, Xoshiro256PlusPlus, Xoshiro256StarStar);
305 
306     static foreach (T; Ts)
307     {
308         static assert(isInfinite!T);
309         static assert(isForwardRange!T);
310         static assert(isUniformRNG!T);
311         static assert(isSeedable!(T, ElementType!T));
312     }
313 }