1 /**
2  * Implementation of the SplitMix64 uniform random number generator.
3  *
4  * Authors:
5  *     $(LINK2 http://braingam.es/, Joseph Rushton Wakeling)
6  *
7  * Copyright:
8  *     Written in 2016 by Joseph Rushton Wakeling.
9  *
10  * License:
11  *     $(LINK2 https://creativecommons.org/publicdomain/zero/1.0/legalcode, Creative Commons CC0)
12  *     (public domain)
13  */
14 module dxorshift.splitmix64;
15 
16 /**
17  * This is a fixed-increment version of Java 8's SplittableRandom
18  * generator.  It is a very fast generator passing BigCrush, and
19  * can be useful if for some reason exactly 64 bits of state are
20  * needed; otherwise, it is suggested to use Xoroshiro128plus
21  * (for moderately parallel computations) or Xorshift1024star
22  * (for massively parallel computations).
23  *
24  * The generator period is 2 ^^ 64.
25  *
26  * Credits:  This implementation is ported from the public-domain
27  *           C implementation by Sebastiano Vigna, available at
28  *           $(LINK http://xoroshiro.di.unimi.it/splitmix64.c)
29  *
30  *           For more details on the SplittableRandom generator,
31  *           see $(LINK http://dx.doi.org/10.1145/2714064.2660195)
32  *           and
33  *    $(LINK http://docs.oracle.com/javase/8/docs/api/java/util/SplittableRandom.html)
34  */
35 struct SplitMix64
36 {
37   public:
38     /// Marks this range as a uniform random number generator
39     enum bool isUniformRandom = true;
40 
41     /// Smallest generated value (0)
42     enum ulong min = ulong.min;
43 
44     /// Largest generated value
45     enum ulong max = ulong.max;
46 
47     /* Copy-by-value is disabled to avoid unintended
48      * duplication of random sequences; use the `dup`
49      * property if you really wish to copy the state
50      * of the RNG.
51      */
52     @disable this(this);
53 
54     // RNG can only be initialized with a seed
55     @disable this();
56 
57     /**
58      * Constructor (RNG instances can only be initialized
59      * with a specified seed).
60      */
61     this(ulong s) @nogc @safe nothrow pure
62     {
63         this.seed(s);
64     }
65 
66     /// Range primitives
67     enum bool empty = false;
68 
69     /// ditto
70     ulong front() @nogc @property @safe const nothrow pure
71     {
72         ulong z = this.state;
73         z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9uL;
74         z = (z ^ (z >> 27)) * 0x94D049BB133111EBuL;
75         return z ^ (z >> 31);
76     }
77 
78     /// ditto
79     void popFront() @nogc @safe nothrow pure
80     {
81         this.state += 0x9E3779B97F4A7C15uL;
82     }
83 
84     /**
85      * Provides a copy of this RNG instance with identical
86      * internal state.
87      *
88      * This property is provided in preference to `save`
89      * so as to allow the user to duplicate RNG state
90      * when explicitly desired, but without risking
91      * unintended copies by functions that save forward
92      * ranges provided as input.
93      */
94     typeof(this) dup() @nogc @property @safe nothrow pure
95     {
96         return typeof(this)(this);
97     }
98 
99     /// (Re)seeds the generator.
100     void seed(ulong s) @nogc @safe nothrow pure
101     {
102         this.state = s;
103         popFront();
104     }
105 
106   private:
107     // 64 bits of state
108     ulong state;
109 
110     // Helper constructor used to implement `dup`
111     this(const ref typeof(this) that) @nogc @safe nothrow pure
112     {
113         this.state = that.state;
114     }
115 }
116 
117 ///
118 unittest
119 {
120     import std.array : array;
121     import std.random : isUniformRNG, randomSample, uniform;
122     import std.range : iota, take;
123     import dxorshift.splitmix64;
124 
125     // splitmix64 generators must be initialized
126     // with a specified seed
127     auto gen = SplitMix64(123456);
128 
129     // verify it is indeed a uniform RNG as defined
130     // in the standard library, whether accessed
131     // directly or via a pointer
132     static assert(isUniformRNG!(typeof(gen)));
133     static assert(isUniformRNG!(typeof(&gen)));
134 
135     // since the postblit is disabled, we must
136     // pass a pointer to any functionality that
137     // would otherwise copy the RNG by value
138     assert((&gen).take(2).array == [4172122716518060777uL,
139                                     4753009419905186825uL]);
140 
141     // this means, of course, that we must guarantee
142     // the lifetime of the pointer is valid for the
143     // lifetime of any functionality that uses it
144     auto sample = iota(100).randomSample(10, &gen).array;
145 
146     // however, we can pass the RNG as-is to any
147     // functionality that takes it by ref and does
148     // not try to copy it by value
149     auto val = uniform!"()"(-0.5, 0.5, gen);
150 
151     // in circumstances where we really want to
152     // copy the RNG state, we can use `dup`
153     auto gen2 = gen.dup;
154     assert((&gen).take(3).array == (&gen2).take(3).array);
155 }
156 
157 unittest
158 {
159     import std.array : array;
160     import std.random : isUniformRNG, isSeedable;
161     import std.range : take;
162 
163     static assert(isUniformRNG!SplitMix64);
164     static assert(isSeedable!SplitMix64);
165     static assert(isSeedable!(SplitMix64, ulong));
166 
167     // output comparisons to reference implementation,
168     // using constructor, seeding, and duplication
169     auto gen = SplitMix64(123456);
170     assert((&gen).take(10).array == [4172122716518060777uL,  4753009419905186825uL,
171                                      10875153875153110245uL, 13339995472625950266uL,
172                                      7648109466873647511uL,  14419900863156435859uL,
173                                      6946445154006067732uL,  16574328997999076320uL,
174                                      13559424511686201017uL, 13754107039689013136uL]);
175 
176     gen.seed(123456);
177     auto gen2 = gen.dup;
178     assert((&gen).take(10).array == [4172122716518060777uL,  4753009419905186825uL,
179                                      10875153875153110245uL, 13339995472625950266uL,
180                                      7648109466873647511uL,  14419900863156435859uL,
181                                      6946445154006067732uL,  16574328997999076320uL,
182                                      13559424511686201017uL, 13754107039689013136uL]);
183 
184     assert((&gen2).take(10).array == [4172122716518060777uL,  4753009419905186825uL,
185                                       10875153875153110245uL, 13339995472625950266uL,
186                                       7648109466873647511uL,  14419900863156435859uL,
187                                       6946445154006067732uL,  16574328997999076320uL,
188                                       13559424511686201017uL, 13754107039689013136uL]);
189 }