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 }