The OpenD Programming Language

1 /++
2 $(SCRIPT inhibitQuickIndex = 1;)
3 
4 Basic API to construct non-uniform random number generators and stochastic algorithms.
5 Non-uniform and uniform random variable can be found at `mir.random.variable`.
6 
7 $(TABLE $(H2 Generation functions),
8 $(TR $(TH Function Name) $(TH Description))
9 $(T2 rand, Generates real, integral, boolean, and enumerated uniformly distributed values.)
10 $(T2 randIndex, Generates uniformly distributed index.)
11 $(T2 randGeometric, Generates geometric distribution with `p = 1/2`.)
12 $(T2 randExponential2, Generates scaled Exponential distribution.)
13 )
14 
15 $(TABLE $(H2 Phobos Compatibility),
16 $(TR $(TH Template Name) $(TH Description))
17 $(T2 PhobosRandom, Extends a Mir random number engine to meet Phobos `std.random` interface)
18 $(T2 isPhobosUniformRNG, Tests if type is a Phobos-style uniform RNG)
19 )
20 
21 Publicly includes  `mir.random.engine`.
22 
23 Authors: Ilya Yaroshenko, Nathan Sashihara
24 Copyright: Copyright, Ilya Yaroshenko 2016-.
25 License:    $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
26 Macros:
27 SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, random, $1)$(NBSP)
28 T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
29 
30 +/
31 module mir.random;
32 
33 import std.traits;
34 import mir.bitop: cttz;
35 import mir.math.common: log2;
36 
37 public import mir.random.engine;
38 
39 version (LDC)
40 {
41     import ldc.intrinsics: llvm_expect;
42     // LDC 1.8.0 supports llvm_expect in CTFE.
43     private template _ctfeExpect(string expr, string expected)
44     {
45         static if (__traits(compiles, { enum a = llvm_expect(123, 456); static assert(a == 123); }))
46             private enum _ctfeExpect = "llvm_expect("~expr~","~expected~")";
47         else
48             private enum _ctfeExpect = expr;
49     }
50 }
51 else version (GNU)
52 {
53     import gcc.builtins: __builtin_expect;
54     private enum _ctfeExpect(string expr, string expected) = `__builtin_expect(`~expr~`,`~expected~`)`;
55 }
56 else
57 {
58     private enum _ctfeExpect(string expr, string expected) = expr;
59 }
60 
61 /++
62 Params:
63     gen = saturated random number generator
64 Returns:
65     Uniformly distributed integer for interval `[T.min .. T.max]`.
66 +/
67 T rand(T, G)(scope ref G gen)
68     if (isSaturatedRandomEngine!G && isIntegral!T && !is(T == enum))
69 {
70     alias R = EngineReturnType!G;
71     enum P = T.sizeof / R.sizeof;
72     static if (P > 1)
73     {
74         _Uab!(R[P],T) u = void;
75         version(LittleEndian)
76             foreach (ref e; u.asArray)
77                 e = gen();
78         else
79             foreach_reverse (ref e; u.asArray)
80                 e = gen();
81         return u.asInteger;
82     }
83     else static if (preferHighBits!G && P == 0)
84     {
85         version(LDC) pragma(inline, true);
86         return cast(T) (gen() >>> ((R.sizeof - T.sizeof) * 8));
87     }
88     else
89     {
90         version(LDC) pragma(inline, true);
91         return cast(T) gen();
92     }
93 }
94 
95 /// ditto
96 T rand(T, G)(scope G* gen)
97     if (isSaturatedRandomEngine!G && isIntegral!T && !is(T == enum))
98 {
99     return rand!(T, G)(*gen);
100 }
101 
102 /// ditto
103 T rand(T)()
104     if (isIntegral!T && !is(T == enum))
105 {
106     return rand!T(rne);
107 }
108 
109 ///
110 @nogc nothrow @safe version(mir_random_test) unittest
111 {
112     auto s = rand!short;
113     auto n = rand!ulong;
114 }
115 
116 ///
117 @nogc nothrow pure @safe version(mir_random_test) unittest
118 {
119     import mir.random.engine.xorshift;
120     auto gen = Xorshift(1);
121     auto s = gen.rand!short;
122     auto n = gen.rand!ulong;
123 }
124 
125 /++
126 Params:
127     gen = saturated random number generator
128 Returns:
129     Uniformly distributed boolean.
130 +/
131 bool rand(T : bool, G)(scope ref G gen)
132     if (isSaturatedRandomEngine!G)
133 {
134     import std.traits : Signed;
135     return 0 > cast(Signed!(EngineReturnType!G)) gen();
136 }
137 
138 /// ditto
139 bool rand(T : bool, G)(scope G* gen)
140     if (isSaturatedRandomEngine!G)
141 {
142     return rand!(T, G)(*gen);
143 }
144 
145 /// ditto
146 bool rand(T : bool)()
147 {
148     return rand!T(rne);
149 }
150 
151 ///
152 @nogc nothrow @safe version(mir_random_test) unittest
153 {
154     auto s = rand!bool;
155 }
156 
157 ///
158 @nogc nothrow pure @safe version(mir_random_test) unittest
159 {
160     import mir.random.engine.xorshift;
161     auto gen = Xorshift(1);
162     auto s = gen.rand!bool;
163 }
164 
165 @nogc nothrow @safe version(mir_random_test) unittest
166 {
167     //Coverage. Impure because uses thread-local.
168     Random* gen = threadLocalPtr!Random;
169     auto s = gen.rand!bool;
170 }
171 
172 private alias Iota(size_t j) = Iota!(0, j);
173 
174 private template Iota(size_t i, size_t j)
175 {
176     import std.meta;
177     static assert(i <= j, "Iota: i should be less than or equal to j");
178     static if (i == j)
179         alias Iota = AliasSeq!();
180     else
181         alias Iota = AliasSeq!(i, Iota!(i + 1, j));
182 }
183 
184 /+
185 Returns pseudo-random integer with the low `bitsWanted` bits set to
186 random values and the remaining high bits all 0.
187 +/
188 private T _randBits(T, uint bitsWanted, G)(scope ref G gen)
189 if (bitsWanted >= 0 && bitsWanted <= T.sizeof * 8
190     && (is(T == uint) || is(T == ulong) || is(T == size_t)))
191 {
192     static if (EngineReturnType!G.sizeof >= T.sizeof)
193         auto bits = gen();
194     else
195         auto bits = gen.rand!T;
196     static if (preferHighBits!G)
197     {
198         enum rshift = (typeof(bits).sizeof * 8) - bitsWanted;
199         return cast(T) (bits >>> rshift);
200     }
201     else
202     {
203         enum mask = (typeof(bits)(1) << bitsWanted) - 1;
204         return cast(T) (bits & typeof(bits)(mask));
205     }
206 }
207 
208 /++
209 Params:
210     gen = saturated random number generator
211 Returns:
212     Uniformly distributed enumeration.
213 +/
214 T rand(T, G)(scope ref G gen)
215     if (isSaturatedRandomEngine!G && is(T == enum))
216 {
217     static if (is(T : long))
218         enum tiny = [EnumMembers!T] == [Iota!(EnumMembers!T.length)];
219     else
220         enum tiny = false;
221     enum n = [EnumMembers!T].length;
222     // If `gen` produces 32 bits or fewer at a time and we have fewer
223     // than 2^^32 elements, use a `uint` index.
224     static if (n <= uint.max && EngineReturnType!G.max <= uint.max)
225         alias IndexType = uint;
226     else
227         alias IndexType = size_t;
228 
229     static if ((n & (n - 1)) == 0)
230     {
231         // Optimized case: power of 2.
232         import core.bitop : bsr;
233         enum bitsWanted = bsr(n);
234         IndexType index = _randBits!(IndexType, bitsWanted)(gen);
235     }
236     else
237     {
238         // General case.
239         IndexType index = gen.randIndex!IndexType(n);
240     }
241 
242     static if (tiny)
243     {
244         return cast(T) index;
245     }
246     else
247     {
248         static immutable T[EnumMembers!T.length] members = [EnumMembers!T];
249         return members[index];
250     }
251 }
252 
253 /// ditto
254 T rand(T, G)(scope G* gen)
255     if (isSaturatedRandomEngine!G && is(T == enum))
256 {
257     return rand!(T, G)(*gen);
258 }
259 
260 /// ditto
261 T rand(T)()
262     if (is(T == enum))
263 {
264     return .rand!T(rne);
265 }
266 
267 ///
268 @nogc nothrow @safe version(mir_random_test) unittest
269 {
270     enum A { a, b, c }
271     auto e = rand!A;
272 }
273 
274 ///
275 @nogc nothrow pure @safe version(mir_random_test) unittest
276 {
277     import mir.random.engine.xorshift;
278     auto gen = Xorshift(1);
279     enum A { a, b, c }
280     auto e = gen.rand!A;
281 }
282 
283 ///
284 @nogc nothrow pure @safe version(mir_random_test) unittest
285 {
286     import mir.random.engine.xorshift;
287     auto gen = Xorshift(1);
288     enum A : dchar { a, b, c }
289     auto e = gen.rand!A;
290 }
291 
292 ///
293 @nogc nothrow pure @safe version(mir_random_test) unittest
294 {
295     import mir.random.engine.xorshift;
296     auto gen = Xorshift(1);
297     enum A : string { a = "a", b = "b", c = "c" }
298     auto e = gen.rand!A;
299 }
300 
301 @nogc nothrow @safe version(mir_random_test) unittest
302 {
303     //Coverage. Impure because uses thread-local.
304     Random* gen = threadLocalPtr!Random;
305     enum A : dchar { a, b, c, d }
306     auto e = gen.rand!A;
307 }
308 
309 private static union _U
310 {
311     real r;
312     struct
313     {
314         version(LittleEndian)
315         {
316             ulong m;
317             ushort e;
318         }
319         else
320         {
321             ushort e;
322             align(2)
323             ulong m;
324         }
325     }
326 }
327 
328 private static union _Uab(A,B) if (A.sizeof == B.sizeof && !is(Unqual!A == Unqual!B))
329 {
330     A a;
331     B b;
332 
333     private import std.traits: isArray, isIntegral, isFloatingPoint;
334 
335     static if (isArray!A && !isArray!B)
336         alias asArray = a;
337     static if (isArray!B && !isArray!A)
338         alias asArray = b;
339 
340     static if (isIntegral!A && !isIntegral!B)
341         alias asInteger = a;
342     static if (isIntegral!B && !isIntegral!A)
343         alias asInteger = b;
344 
345     static if (isFloatingPoint!A && !isFloatingPoint!B)
346         alias asFloatingPoint = a;
347     static if (isFloatingPoint!B && !isFloatingPoint!A)
348         alias asFloatingPoint = b;
349 }
350 
351 /++
352 Params:
353     gen = saturated random number generator
354     boundExp = bound exponent (optional). `boundExp` must be less or equal to `T.max_exp`.
355 Returns:
356     Uniformly distributed real for interval `(-2^^boundExp , 2^^boundExp)`.
357 Note: `fabs` can be used to get a value from positive interval `[0, 2^^boundExp$(RPAREN)`.
358 +/
359 T rand(T, G)(scope ref G gen, sizediff_t boundExp = 0)
360     if (isSaturatedRandomEngine!G && isFloatingPoint!T)
361 {
362     assert(boundExp <= T.max_exp);
363     static if (T.mant_dig == float.mant_dig)
364     {
365         enum W = T.sizeof * 8 - T.mant_dig;//8
366         _Uab!(int,float) u = void;
367         u.asInteger = gen.rand!uint;
368         enum uint EXPMASK = 0x7F80_0000;
369         boundExp -= T.min_exp - 1;
370         size_t exp = EXPMASK & u.asInteger;
371         exp = boundExp - (exp ? cttz(exp) - (T.mant_dig - 1) : gen.randGeometric + W);
372         u.asInteger &= ~EXPMASK;
373         if(cast(sizediff_t)exp < 0)
374         {
375             exp = -cast(sizediff_t)exp;
376             uint m = u.asInteger & int.max;
377             if(exp >= T.mant_dig)
378                 m = 0;
379             else
380                 m >>= cast(uint)exp;
381             u.asInteger = (u.asInteger & ~int.max) ^ m;
382             exp = 0;
383         }
384         u.asInteger = cast(uint)(exp << (T.mant_dig - 1)) ^ u.asInteger;
385         return u.asFloatingPoint;
386     }
387     else
388     static if (T.mant_dig == double.mant_dig)
389     {
390         enum W = T.sizeof * 8 - T.mant_dig; //11
391         _Uab!(long,double) u = void;
392         u.asInteger = gen.rand!ulong;
393         enum ulong EXPMASK = 0x7FF0_0000_0000_0000;
394         boundExp -= T.min_exp - 1;
395         ulong exp = EXPMASK & u.asInteger;
396         exp = ulong(boundExp) - (exp ? cttz(exp) - (T.mant_dig - 1) : gen.randGeometric + W);
397         u.asInteger &= ~EXPMASK;
398         if(cast(long)exp < 0)
399         {
400             exp = -cast(sizediff_t)exp;
401             ulong m = u.asInteger & long.max;
402             if(exp >= T.mant_dig)
403                 m = 0;
404             else
405                 m >>= cast(uint)exp;
406             u.asInteger = (u.asInteger & ~long.max) ^ m;
407             exp = 0;
408         }
409         u.asInteger = (exp << (T.mant_dig - 1)) ^ u.asInteger;
410         return u.asFloatingPoint;
411     }
412     else
413     static if (T.mant_dig == 64)
414     {
415         enum W = 15;
416         auto d = gen.rand!uint;
417         auto m = gen.rand!ulong;
418         enum uint EXPMASK = 0x7FFF;
419         boundExp -= T.min_exp - 1;
420         size_t exp = EXPMASK & d;
421         exp = boundExp - (exp ? cttz(exp) : gen.randGeometric + W);
422         if (cast(sizediff_t)exp > 0)
423             m |= ~long.max;
424         else
425         {
426             m &= long.max;
427             exp = -cast(sizediff_t)exp;
428             if(exp >= T.mant_dig)
429                 m = 0;
430             else
431                 m >>= cast(uint)exp;
432             exp = 0;
433         }
434         d = cast(uint) exp ^ (d & ~EXPMASK);
435         _U ret = void;
436         ret.e = cast(ushort)d;
437         ret.m = m;
438         return ret.r;
439     }
440     /// TODO: quadruple
441     else static assert(0);
442 }
443 
444 /// ditto
445 T rand(T, G)(scope G* gen, sizediff_t boundExp = 0)
446     if (isSaturatedRandomEngine!G && isFloatingPoint!T)
447 {
448     return rand!(T, G)(*gen, boundExp);
449 }
450 
451 /// ditto
452 T rand(T)(sizediff_t boundExp = 0)
453     if (isFloatingPoint!T)
454 {
455     return rand!T(rne, boundExp);
456 }
457 
458 
459 ///
460 @nogc nothrow @safe version(mir_random_test) unittest
461 {
462     import mir.math.common: fabs;
463     
464     auto a = rand!float;
465     assert(-1 < a && a < +1);
466 
467     auto b = rand!double(4);
468     assert(-16 < b && b < +16);
469     
470     auto c = rand!double(-2);
471     assert(-0.25 < c && c < +0.25);
472     
473     auto d = rand!real.fabs;
474     assert(0.0L <= d && d < 1.0L);
475 }
476 
477 ///
478 @nogc nothrow pure @safe version(mir_random_test) unittest
479 {
480     import mir.math.common: fabs;
481     import mir.random.engine.xorshift;
482     auto gen = Xorshift(1);
483     
484     auto a = gen.rand!float;
485     assert(-1 < a && a < +1);
486 
487     auto b = gen.rand!double(4);
488     assert(-16 < b && b < +16);
489     
490     auto c = gen.rand!double(-2);
491     assert(-0.25 < c && c < +0.25);
492     
493     auto d = gen.rand!real.fabs;
494     assert(0.0L <= d && d < 1.0L);
495 }
496 
497 /// Subnormal numbers
498 @nogc nothrow pure @safe version(mir_random_test) unittest
499 {
500     import mir.random.engine.xorshift;
501     auto gen = Xorshift(1);
502     auto x = gen.rand!double(double.min_exp-1);
503     assert(-double.min_normal < x && x < double.min_normal);
504 }
505 
506 @nogc nothrow @safe version(mir_random_test) unittest
507 {
508     //Coverage. Impure because uses thread-local.
509     import mir.math.common: fabs;
510     import std.meta: AliasSeq;
511     
512     auto a = rne.rand!float;
513     assert(-1 < a && a < +1);
514 
515     auto b = rne.rand!double(4);
516     assert(-16 < b && b < +16);
517     
518     auto c = rne.rand!double(-2);
519     assert(-0.25 < c && c < +0.25);
520     
521     auto d = rne.rand!real.fabs;
522     assert(0.0L <= d && d < 1.0L);
523 
524     foreach(T; AliasSeq!(float, double, real))
525     {
526         auto f = rne.rand!T(T.min_exp-1);
527         assert(f.fabs < T.min_normal, T.stringof);
528     }
529 }
530 
531 /++
532 Params:
533     gen = uniform random number generator
534     m = positive module
535 Returns:
536     Uniformly distributed integer for interval `[0 .. m$(RPAREN)`.
537 +/
538 T randIndex(T, G)(scope ref G gen, T _m)
539     if(isSaturatedRandomEngine!G && isUnsigned!T)
540 {
541     immutable m = _m + 0u;
542     static if (EngineReturnType!G.sizeof >= T.sizeof * 2)
543         alias MaybeR = EngineReturnType!G;
544     else static if (uint.sizeof >= T.sizeof * 2)
545         alias MaybeR = uint;
546     else static if (ulong.sizeof >= T.sizeof * 2)
547         alias MaybeR = ulong;
548     else static if (is(ucent) && __traits(compiles, {static assert(ucent.sizeof >= T.sizeof * 2);}))
549         mixin ("alias MaybeR = ucent;");
550     else
551         alias MaybeR = void;
552 
553     static if (!is(MaybeR == void))
554     {
555         alias R = MaybeR;
556         static assert(R.sizeof >= T.sizeof * 2);
557         //Use Daniel Lemire's fast alternative to modulo reduction:
558         //https://lemire.me/blog/2016/06/30/fast-random-shuffling/
559         R randombits = cast(R) gen.rand!T;
560         R multiresult = randombits * m;
561         T leftover = cast(T) multiresult;
562         if (mixin(_ctfeExpect!(`leftover < m`, `false`)))
563         {
564             immutable threshold = -m % m ;
565             while (leftover < threshold)
566             {
567                 randombits =  cast(R) gen.rand!T;
568                 multiresult = randombits * m;
569                 leftover = cast(T) multiresult;
570             }
571         }
572         enum finalshift = T.sizeof * 8;
573         return cast(T) (multiresult >>> finalshift);
574     }
575     else
576     {
577         import mir.utility : extMul;
578         //Use Daniel Lemire's fast alternative to modulo reduction:
579         //https://lemire.me/blog/2016/06/30/fast-random-shuffling/
580         auto u = extMul!T(gen.rand!T, m);
581         if (mixin(_ctfeExpect!(`u.low < m`, `false`)))
582         {
583             immutable T threshold = -m % m;
584             while (u.low < threshold)
585             {
586                 u = extMul!T(gen.rand!T, m);
587             }
588         }
589         return u.high;
590     }
591 }
592 
593 /// ditto
594 T randIndex(T, G)(scope G* gen, T m)
595     if(isSaturatedRandomEngine!G && isUnsigned!T)
596 {
597     return randIndex!(T, G)(*gen, m);
598 }
599 
600 /// ditto
601 T randIndex(T)(T m)
602     if(isUnsigned!T)
603 {
604     return randIndex!T(rne, m);
605 }
606 
607 ///
608 @nogc nothrow @safe version(mir_random_test) unittest
609 {
610     auto s = randIndex(100u);
611     auto n = randIndex!ulong(-100);
612 }
613 
614 ///
615 @nogc nothrow pure @safe version(mir_random_test) unittest
616 {
617     import mir.random;
618     import mir.random.engine.xorshift;
619     auto gen = Xorshift(1);
620     auto s = gen.randIndex!uint(100);
621     auto n = gen.randIndex!ulong(-100);
622 }
623 
624 @nogc nothrow pure @safe version(mir_random_test) unittest
625 {
626     //CTFE check.
627     import std.meta : AliasSeq;
628     import mir.random.engine.xoshiro : Xoroshiro128Plus;
629     foreach (IntType; AliasSeq!(ubyte,ushort,uint,ulong))
630     {
631         enum IntType e = (){auto g = Xoroshiro128Plus(1); return g.randIndex!IntType(100);}();
632         auto gen = Xoroshiro128Plus(1);
633         assert(e == gen.randIndex!IntType(100));
634     }
635 }
636 
637 @nogc nothrow pure @safe version(mir_random_test) unittest
638 {
639     //Test production of ulong from ulong generator.
640     import mir.random.engine.xoshiro;
641     auto gen = Xoroshiro128Plus(1);
642     enum ulong limit = 10;
643     enum count = 10;
644     ulong[limit] buckets;
645     foreach (_; 0 .. count)
646     {
647         ulong x = gen.randIndex!ulong(limit);
648         assert(x < limit);
649         buckets[cast(size_t) x] += 1;
650     }
651     foreach (i, x; buckets)
652         assert(x != count, "All values were the same!");
653 }
654 
655 @nogc nothrow @safe version(mir_random_test) unittest
656 {
657     //Coverage. Impure because uses thread-local.
658     Random* gen = threadLocalPtr!Random;
659     auto s = gen.randIndex!uint(100);
660     auto n = gen.randIndex!ulong(-100);
661 }
662 
663 /++
664     Returns: `n >= 0` such that `P(n) := 1 / (2^^(n + 1))`.
665 +/
666 size_t randGeometric(G)(scope ref G gen)
667     if(isSaturatedRandomEngine!G)
668 {
669     alias R = EngineReturnType!G;
670     static if (R.sizeof >= size_t.sizeof)
671         alias T = size_t;
672     else
673         alias T = R;
674     for(size_t count = 0;; count += T.sizeof * 8)
675         if(auto val = gen.rand!T())
676             return count + cttz(val);
677 }
678 
679 /// ditto
680 size_t randGeometric(G)(scope G* gen)
681     if(isSaturatedRandomEngine!G)
682 {
683     return randGeometric!(G)(*gen);
684 }
685 
686 /// ditto
687 size_t randGeometric()()
688 {
689     return randGeometric(rne);
690 }
691 
692 ///
693 @nogc nothrow @safe version(mir_random_test) unittest
694 {
695     size_t s = randGeometric;
696 }
697 
698 ///
699 @nogc nothrow pure @safe version(mir_random_test) unittest
700 {
701     import mir.random.engine.xoshiro;
702     auto gen = Xoroshiro128Plus(1);
703 
704     size_t s = gen.randGeometric;
705 }
706 
707 /++
708 Params:
709     gen = saturated random number generator
710 Returns:
711     `X ~ Exp(1) / log(2)`.
712 Note: `fabs` can be used to get a value from positive interval `[0, 2^^boundExp$(RPAREN)`.
713 +/
714 T randExponential2(T, G)(scope ref G gen)
715     if (isSaturatedRandomEngine!G && isFloatingPoint!T)
716 {
717     enum W = T.sizeof * 8 - T.mant_dig - 1 - bool(T.mant_dig == 64);
718     static if (is(T == float))
719     {
720         _Uab!(uint,float) u = void;
721         u.asInteger = gen.rand!uint;
722         enum uint EXPMASK = 0xFF80_0000;
723         auto exp = EXPMASK & u.asInteger;
724         u.asInteger &= ~EXPMASK;
725         u.asInteger ^= 0x3F000000; // 0.5
726         auto y = exp ? cttz(exp) - (T.mant_dig - 1) : gen.randGeometric + W;
727         auto x = u.asFloatingPoint;
728     }
729     else
730     static if (is(T == double))
731     {
732         _Uab!(ulong,double) u = void;
733         u.asInteger = gen.rand!ulong;
734         enum ulong EXPMASK = 0xFFF0_0000_0000_0000;
735         auto exp = EXPMASK & u.asInteger;
736         u.asInteger &= ~EXPMASK;
737         u.asInteger ^= 0x3FE0000000000000; // 0.5
738         auto y = exp ? cttz(exp) - (T.mant_dig - 1) : gen.randGeometric + W;
739         auto x = u.asFloatingPoint;
740     }
741     else
742     static if (T.mant_dig == 64)
743     {
744         _U ret = void;
745         ret.e = 0x3FFE;
746         ret.m = gen.rand!ulong | ~long.max;
747         auto y = gen.randGeometric;
748         auto x = ret.r;
749     }
750     /// TODO: quadruple
751     else static assert(0);
752 
753     if (x == 0.5f)
754         return y;
755     else
756         return -log2(x) + y;
757 }
758 
759 /// ditto
760 T randExponential2(T, G)(scope G* gen)
761     if (isSaturatedRandomEngine!G && isFloatingPoint!T)
762 {
763     return randExponential2!(T, G)(*gen);
764 }
765 
766 /// ditto
767 T randExponential2(T)()
768     if (isFloatingPoint!T)
769 {
770     return randExponential2!T(rne);
771 }
772 
773 ///
774 @nogc nothrow @safe version(mir_random_test) unittest
775 {
776     auto v = randExponential2!double;
777 }
778 
779 ///
780 @nogc nothrow @safe pure version(mir_random_test) unittest
781 {
782     import mir.random.engine.xorshift;
783     auto gen = Xorshift(1);
784     auto v = gen.randExponential2!double();
785 }
786 
787 /++
788 $(LINK2 https://dlang.org/phobos/std_random.html#.isUniformRNG,
789 Tests if T is a Phobos-style uniform RNG.)
790 +/
791 template isPhobosUniformRNG(T)
792 {
793     import std.random: isUniformRNG;
794     enum bool isPhobosUniformRNG = isUniformRNG!T;
795 }
796 
797 /++
798 Extends a Mir-style random number generator to also be a Phobos-style
799 uniform RNG. If `Engine` is already a Phobos-style uniform RNG,
800 `PhobosRandom` is just an alias for `Engine`.
801 +/
802 struct PhobosRandom(Engine) if (isRandomEngine!Engine && !isPhobosUniformRNG!Engine)//Doesn't need to be saturated.
803 {
804     alias Uint = EngineReturnType!Engine;
805     private Engine _engine;
806     private Uint _front;
807 
808     /// Default constructor and copy constructor are disabled.
809     @disable this();
810     /// ditto
811     @disable this(this);
812 
813     /// Forward constructor arguments to `Engine`.
814     this(A...)(auto ref A args)
815     if (is(typeof(Engine(args))))
816     {
817         _engine = Engine(args);
818         _front = _engine.opCall();
819     }
820 
821     /++
822     Phobos-style random interface.
823 
824     `save` is only available when the underlying `Engine` has no indirections
825     and has `pure @safe opCall()` and doesn't have an impure or `@system`
826     destructor.
827     +/
828     enum bool isUniformRandom = true;
829     /// ditto
830     enum Uint min = Uint.min;//Always normalized.
831     /// ditto
832     enum Uint max = Engine.max;//Might not be saturated.
833     /// ditto
834     enum bool empty = false;
835     /// ditto
836     @property Uint front()() const { return _front; }
837     /// ditto
838     void popFront()() { _front = _engine.opCall(); }
839     /// ditto
840     void seed(A...)(auto ref A args) if (is(typeof(Engine(args))))
841     {
842         _engine.__ctor(args);
843         _front = _engine.opCall();
844     }
845     /// ditto
846     static if (!hasIndirections!Engine && is(typeof(() pure @safe {
847         Engine e = Engine.init;
848         return e();
849     }())))
850     @property typeof(this) save()() const @trusted
851     {
852         import std.meta: allSatisfy;
853 
854         typeof(return) copy = void;
855         static if (allSatisfy!(_isPOD, typeof(Engine.tupleof)))
856         {
857             // The advantage of fieldwise assignment instead of memcpy is that
858             // it works during CTFE.
859             foreach (i, ref e; this.tupleof)
860             {
861                 static if (__traits(isPOD, typeof(e)))
862                     copy.tupleof[i] = e;
863                 else
864                     foreach (i2, ref e2; e.tupleof)
865                         copy.tupleof[i].tupleof[i2] = e2;
866             }
867         }
868         else
869         {
870             enum N = typeof(this).sizeof;
871             (cast(ubyte*) &copy)[0 .. N] = (cast(const ubyte*) &this)[0 .. N];
872         }
873         return copy;
874     }
875 
876     private enum _isPOD(T) = __traits(isPOD, T);
877 
878     /// Retain support for Mir-style random interface.
879     enum bool isRandomEngine = true;
880     /// ditto
881     enum bool preferHighBits = .preferHighBits!Engine;
882     /// ditto
883     Uint opCall()()
884     {
885         Uint result = _front;
886         _front = _engine.opCall();
887         return result;
888     }
889 
890     ///
891     @property ref inout(Engine) engine()() inout @nogc nothrow pure @safe
892     {
893         return _engine;
894     }
895 }
896 
897 /// ditto
898 template PhobosRandom(Engine) if (isRandomEngine!Engine && isPhobosUniformRNG!Engine)
899 {
900     alias PhobosRandom = Engine;
901 }
902 
903 ///
904 @nogc nothrow pure @safe version(mir_random_test) unittest
905 {
906     import mir.random.engine.xorshift: Xorshift1024StarPhi;
907     import std.random: isSeedable, isPhobosUniformRNG = isUniformRNG;
908     import std.range.primitives: isForwardRange;
909 
910     alias RNG = PhobosRandom!Xorshift1024StarPhi;
911 
912     //Phobos interface
913     static assert(isPhobosUniformRNG!(RNG, ulong));
914     static assert(isSeedable!(RNG, ulong));
915     static assert(isForwardRange!RNG);
916     //Mir interface
917     static assert(isSaturatedRandomEngine!RNG);
918     static assert(is(EngineReturnType!RNG == ulong));
919 
920     auto gen = Xorshift1024StarPhi(1);
921     auto rng = RNG(1);
922     assert(gen() == rng.front);
923     rng.popFront();
924     assert(gen() == rng.front);
925     rng.popFront();
926     assert(gen() == rng());
927 
928     gen.__ctor(1);
929     rng.seed(1);
930     assert(gen() == rng());
931 }
932 
933 @nogc nothrow pure @safe version(mir_random_test) unittest
934 {
935     import mir.random.engine.xorshift: Xorshift1024StarPhi;
936 
937     // Test .save works for PhobosRandom.
938     auto gen1 = PhobosRandom!Xorshift1024StarPhi(123456789);
939     auto gen2 = gen1.save;
940     const a = gen1();
941     const b = gen1();
942     assert(a == gen2());
943     assert(b == gen2());
944 }