The OpenD Programming Language

1 /++
2 Linear Congruential generator.
3 
4 Copyright: Copyright Andrei Alexandrescu 2008 - 2009, Ilya Yaroshenko 2016-.
5 License:   $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
6 Authors: $(HTTP erdani.org, Andrei Alexandrescu) Ilya Yaroshenko (rework)
7 +/
8 module mir.random.engine.linear_congruential;
9 
10 import std.traits;
11 
12 /++
13 Linear Congruential generator.
14 +/
15 struct LinearCongruentialEngine(Uint, Uint a, Uint c, Uint m)
16     if (isUnsigned!Uint)
17 {
18     ///
19     enum isRandomEngine = true;
20 
21     /// Highest generated value (`modulus - 1 - bool(c == 0)`).
22     enum Uint max = m - 1 - bool(c == 0);
23 /**
24 The parameters of this distribution. The random number is $(D_PARAM x
25 = (x * multiplier + increment) % modulus).
26  */
27     enum Uint multiplier = a;
28     ///ditto
29     enum Uint increment = c;
30     ///ditto
31     enum Uint modulus = m;
32 
33     static assert(m == 0 || a < m);
34     static assert(m == 0 || c < m);
35     static assert(m == 0 || (cast(ulong)a * (m-1) + c) % m == (c < a ? c - a + m : c - a));
36 
37     /++
38     The low bits of a linear congruential generator whose modulus is a
39     power of 2 have a much shorter period than the high bits.
40     Note that for LinearCongruentialEngine, `modulus == 0` signifies
41     a modulus of `2 ^^ (Uint.sizeof*8)` which is not representable as `Uint`.
42     +/
43     enum bool preferHighBits = 0 == (modulus & (modulus - 1));
44 
45     @disable this();
46     @disable this(this);
47 
48     // Check for maximum range
49     private static ulong gcd()(ulong a, ulong b)
50     {
51         while (b)
52         {
53             auto t = b;
54             b = a % b;
55             a = t;
56         }
57         return a;
58     }
59 
60     private static ulong primeFactorsOnly()(ulong n)
61     {
62         ulong result = 1;
63         ulong iter = 2;
64         for (; n >= iter * iter; iter += 2 - (iter == 2))
65         {
66             if (n % iter) continue;
67             result *= iter;
68             do
69             {
70                 n /= iter;
71             } while (n % iter == 0);
72         }
73         return result * n;
74     }
75 
76     @safe pure nothrow version(mir_random_test) unittest
77     {
78         static assert(primeFactorsOnly(100) == 10);
79         static assert(primeFactorsOnly(11) == 11);
80         static assert(primeFactorsOnly(7 * 7 * 7 * 11 * 15 * 11) == 7 * 11 * 15);
81         static assert(primeFactorsOnly(129 * 2) == 129 * 2);
82         // enum x = primeFactorsOnly(7 * 7 * 7 * 11 * 15);
83         // static assert(x == 7 * 11 * 15);
84     }
85 
86     private static bool properLinearCongruentialParameters()(ulong m,ulong a, ulong c)
87     {
88         if (m == 0)
89         {
90             static if (is(Uint == uint))
91             {
92                 // Assume m is uint.max + 1
93                 m = (1uL << 32);
94             }
95             else
96             {
97                 return false;
98             }
99         }
100         // Bounds checking
101         if (a == 0 || a >= m || c >= m) return false;
102         // c and m are relatively prime
103         if (c > 0 && gcd(c, m) != 1) return false;
104         // a - 1 is divisible by all prime factors of m
105         if ((a - 1) % primeFactorsOnly(m)) return false;
106         // if a - 1 is multiple of 4, then m is a  multiple of 4 too.
107         if ((a - 1) % 4 == 0 && m % 4) return false;
108         // Passed all tests
109         return true;
110     }
111 
112     // check here
113     static assert(c == 0 || properLinearCongruentialParameters(m, a, c),
114             "Incorrect instantiation of LinearCongruentialEngine");
115 
116 /**
117 Constructs a $(D_PARAM LinearCongruentialEngine) generator seeded with
118 `x0`.
119 Params:
120     x0 = seed, must be positive if c equals to 0.
121  */
122     this(Uint x0) @safe pure nothrow @nogc
123     {
124         _x = modulus ? (x0 % modulus) : x0;
125         static if (c == 0)
126         {
127             //Necessary to prevent generator from outputting an endless series of zeroes.
128             if (_x == 0)
129                 _x = max;
130         }
131     }
132 
133     /**
134        Advances the random sequence.
135     */
136     Uint opCall() @safe pure nothrow @nogc
137     {
138         static if (m)
139         {
140             static if (is(Uint == uint))
141             {
142                 static if (m == uint.max)
143                 {
144                     immutable ulong
145                         x = (cast(ulong) a * _x + c),
146                         v = x >> 32,
147                         w = x & uint.max;
148                     immutable y = cast(uint)(v + w);
149                     _x = (y < v || y == uint.max) ? (y + 1) : y;
150                 }
151                 else static if (m == int.max)
152                 {
153                     immutable ulong
154                         x = (cast(ulong) a * _x + c),
155                         v = x >> 31,
156                         w = x & int.max;
157                     immutable uint y = cast(uint)(v + w);
158                     _x = (y >= int.max) ? (y - int.max) : y;
159                 }
160                 else
161                 {
162                     _x = cast(uint) ((cast(ulong) a * _x + c) % m);
163                 }
164             }
165             else static assert(0);
166         }
167         else
168         {
169             _x = a * _x + c;
170         }
171         static if (c == 0)
172             return _x - 1;
173         else
174             return _x;
175     }
176 
177     private Uint _x;
178 }
179 
180 /**
181 Define $(D_PARAM LinearCongruentialEngine) generators with well-chosen
182 parameters. `MinstdRand0` implements Park and Miller's "minimal
183 standard" $(HTTP
184 wikipedia.org/wiki/Park%E2%80%93Miller_random_number_generator,
185 generator) that uses 16807 for the multiplier. `MinstdRand`
186 implements a variant that has slightly better spectral behavior by
187 using the multiplier 48271. Both generators are rather simplistic.
188  */
189 alias MinstdRand0 = LinearCongruentialEngine!(uint, 16807, 0, 2147483647);
190 /// ditto
191 alias MinstdRand = LinearCongruentialEngine!(uint, 48271, 0, 2147483647);
192 
193 ///
194 @safe version(mir_random_test) unittest
195 {
196     import mir.random.engine;
197     // seed with a constant
198     auto rnd0 = MinstdRand0(1);
199     auto n = rnd0(); // same for each run
200     // Seed with an unpredictable value
201     rnd0 = MinstdRand0(cast(uint)unpredictableSeed);
202     n = rnd0(); // different across runs
203 
204     import std.traits;
205     static assert(is(ReturnType!rnd0 == uint));
206 }
207 
208 @safe version(mir_random_test) unittest
209 {
210     auto rnd0 = MinstdRand0(MinstdRand0.modulus);
211     auto n = rnd0();
212     assert(n != rnd0());
213 }
214 
215 version(mir_random_test) unittest
216 {
217     import mir.random.engine;
218     static assert(isRandomEngine!MinstdRand);
219     static assert(isRandomEngine!MinstdRand0);
220 
221     static assert(!isSaturatedRandomEngine!MinstdRand);
222     static assert(!isSaturatedRandomEngine!MinstdRand0);
223 
224     // The correct numbers are taken from The Database of Integer Sequences
225     // http://www.research.att.com/~njas/sequences/eisBTfry00128.txt
226     auto checking0 = [
227         16807,282475249,1622650073,984943658,1144108930,470211272,
228         101027544,1457850878,1458777923,2007237709,823564440,1115438165,
229         1784484492,74243042,114807987,1137522503,1441282327,16531729,
230         823378840,143542612 ];
231 
232     auto rnd0 = MinstdRand0(1);
233 
234     foreach (e; checking0)
235     {
236         assert(rnd0() == e - 1);
237     }
238     // Test the 10000th invocation
239     // Correct value taken from:
240     // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2461.pdf
241     rnd0 = MinstdRand0(1);
242     foreach(_; 0 .. 9999)
243         rnd0();
244     assert(rnd0() == 1043618065 - 1);
245 
246     // Test MinstdRand
247     auto checking = [48271UL,182605794,1291394886,1914720637,2078669041,
248                      407355683];
249     auto rnd = MinstdRand(1);
250     foreach (e; checking)
251     {
252         assert(rnd() == e - 1);
253     }
254 
255     // Test the 10000th invocation
256     // Correct value taken from:
257     // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2461.pdf
258     rnd = MinstdRand(1);
259     foreach(_; 0 .. 9999)
260         rnd();
261     assert(rnd() == 399268537 - 1);
262 }