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*) ©)[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 }