1 /// 2 module unexpected.pcg; 3 4 import std.meta : AliasSeq; 5 import std.random : isUniformRNG; 6 import std.traits : isIntegral; 7 8 private V rotr(V)(V value, uint r) { 9 return cast(V)(value >> r | value << (-r & (V.sizeof * 8 - 1))); 10 } 11 12 struct PCGConsts(X, I) { 13 import std.math.exponential : log2; 14 enum spareBits = (I.sizeof - X.sizeof) * 8; 15 enum wantedOpBits = cast(uint)log2(X.sizeof * 8.0); 16 struct xshrr { 17 enum opBits = spareBits >= wantedOpBits ? wantedOpBits : spareBits; 18 enum amplifier = wantedOpBits - opBits; 19 enum xShift = (opBits + X.sizeof * 8) / 2; 20 enum mask = (1 << opBits) - 1; 21 enum bottomSpare = spareBits - opBits; 22 } 23 struct xshrs { 24 // there must be a simpler way to express this 25 static if (spareBits - 5 >= 64) { 26 enum opBits = 5; 27 } else static if (spareBits - 4 >= 32) { 28 enum opBits = 4; 29 } else static if (spareBits - 3 >= 16) { 30 enum opBits = 3; 31 } else static if (spareBits - 2 >= 4) { 32 enum opBits = 2; 33 } else static if (spareBits - 1 >= 1) { 34 enum opBits = 1; 35 } else { 36 enum opBits = 0; 37 } 38 enum xShift = opBits + ((X.sizeof * 8) + mask) / 2; 39 enum mask = (1 << opBits) - 1; 40 enum bottomSpare = spareBits - opBits; 41 } 42 struct xsh { 43 enum topSpare = 0; 44 enum bottomSpare = spareBits - topSpare; 45 enum xShift = (topSpare + X.sizeof * 8) / 2; 46 } 47 struct xsl { 48 enum topSpare = spareBits; 49 enum bottomSpare = spareBits - topSpare; 50 enum xShift = (topSpare + X.sizeof * 8) / 2; 51 } 52 struct rxs { 53 enum shift = (I.sizeof - X.sizeof) * 8; 54 // there must be a simpler way to express this 55 static if (shift > 64 + 8) { 56 enum rShiftAmount = I.sizeof - 6; 57 enum rShiftMask = 63; 58 } else static if (shift > 32 + 4) { 59 enum rShiftAmount = I.sizeof - 5; 60 enum rShiftMask = 31; 61 } else static if (shift > 16 + 2) { 62 enum rShiftAmount = I.sizeof - 4; 63 enum rShiftMask = 15; 64 } else static if (shift > 8 + 1) { 65 enum rShiftAmount = I.sizeof - 3; 66 enum rShiftMask = 7; 67 } else static if (shift > 4 + 1) { 68 enum rShiftAmount = I.sizeof - 2; 69 enum rShiftMask = 3; 70 } else static if (shift > 2 + 1) { 71 enum rShiftAmount = I.sizeof - 1; 72 enum rShiftMask = 1; 73 } else { 74 enum rShiftAmount = 0; 75 enum rShiftMask = 0; 76 } 77 enum extraShift = (X.sizeof - shift)/2; 78 } 79 struct rxsm { 80 enum opBits = cast(uint)log2(X.sizeof * 8.0) - 1; 81 enum shift = (I.sizeof - X.sizeof) * 8; 82 enum mask = (1 << opBits) - 1; 83 } 84 struct xslrr { 85 enum opBits = spareBits >= wantedOpBits ? wantedOpBits : spareBits; 86 enum amplifier = wantedOpBits - opBits; 87 enum mask = (1 << opBits) - 1; 88 enum topSpare = spareBits; 89 enum bottomSpare = spareBits - topSpare; 90 enum xShift = (topSpare + X.sizeof * 8) / 2; 91 } 92 } 93 94 private X xorshift(X, I)(I tmp, uint amt1, uint amt2) { 95 tmp ^= tmp >> amt1; 96 return cast(X)(tmp >> amt2); 97 } 98 99 /// XSH RR (xorshift high, random rotate) - decent performance, slightly better results 100 private X xshrr(X, I)(const I state) { 101 alias constants = PCGConsts!(X, I).xshrr; 102 static if (constants.opBits > 0) { 103 auto rot = (state >> (I.sizeof * 8 - constants.opBits)) & constants.mask; 104 } else { 105 enum rot = 0; 106 } 107 uint amprot = cast(uint)((rot << constants.amplifier) & constants.mask); 108 I tmp = state; 109 return rotr(xorshift!X(tmp, constants.xShift, constants.bottomSpare), amprot); 110 } 111 112 /// XSH RS (xorshift high, random shift) - decent performance 113 private X xshrs(X, I)(const I state) { 114 alias constants = PCGConsts!(X, I).xshrs; 115 static if (constants.opBits > 0) { 116 uint rshift = (state >> (I.sizeof * 8 - constants.opBits)) & constants.mask; 117 } else { 118 uint rshift = 0; 119 } 120 I tmp = state; 121 return xorshift!X(tmp, constants.xShift, cast(uint)(constants.bottomSpare - constants.mask + rshift)); 122 } 123 124 /// XSH (fixed xorshift, high) - don't use this for anything smaller than ulong 125 private X xsh(X, I)(const I state) { 126 alias constants = PCGConsts!(X, I).xsh; 127 128 I tmp = state; 129 return xorshift!X(tmp, constants.xShift, constants.bottomSpare); 130 } 131 132 /// XSL (fixed xorshift, low) - don't use this for anything smaller than ulong 133 private X xsl(X, I)(const I state) { 134 alias constants = PCGConsts!(X, I).xsl; 135 136 I tmp = state; 137 return xorshift!X(tmp, constants.xShift, constants.bottomSpare); 138 } 139 140 /// RXS (random xorshift) 141 private X rxs(X, I)(const I state) { 142 alias constants = PCGConsts!(X, I).rxs; 143 uint rshift = (state >> constants.rShiftAmount) & constants.rShiftMask; 144 I tmp = state; 145 return xorshift!X(tmp, cast(uint)(constants.shift + constants.extraShift - rshift), rshift); 146 } 147 148 /++ 149 RXS M XS (random xorshift, multiply, fixed xorshift) 150 This has better statistical properties, but supposedly performs worse. This 151 was not reproducible, however. 152 +/ 153 private X rxsmxs(X, I)(const I state) { 154 X result = rxsm!X(state); 155 result ^= result >> ((2 * X.sizeof * 8 + 2) / 3); 156 return result; 157 } 158 159 /// RXS M (random xorshift, multiply) 160 private X rxsm(X, I)(const I state) { 161 alias constants = PCGConsts!(X, I).rxsm; 162 I tmp = state; 163 static if (constants.opBits > 0) { 164 uint rshift = (tmp >> (I.sizeof * 8 - constants.opBits)) & constants.mask; 165 } else { 166 uint rshift = 0; 167 } 168 tmp ^= tmp >> (constants.opBits + rshift); 169 tmp *= PCGMMultiplier!I; 170 return cast(X)(tmp >> constants.shift); 171 } 172 173 /// DXSM (double xorshift, multiply) - newer, better performance for types 2x the size of the largest type the cpu can handle 174 private X dxsm(X, I)(const I state) { 175 static assert(X.sizeof <= I.sizeof/2, "Output type must be half the size of the state type."); 176 X hi = cast(X)(state >> ((I.sizeof - X.sizeof) * 8)); 177 X lo = cast(X)state; 178 179 lo |= 1; 180 hi ^= hi >> (X.sizeof * 8 / 2); 181 hi *= PCGMMultiplier!I; 182 hi ^= hi >> (3*(X.sizeof * 8 / 4)); 183 hi *= lo; 184 return hi; 185 } 186 /// XSL RR (fixed xorshift, random rotate) - better performance for types 2x the size of the largest type the cpu can handle 187 private X xslrr(X, I)(const I state) { 188 alias constants = PCGConsts!(X, I).xslrr; 189 190 I tmp = state; 191 static if (constants.opBits > 0) { 192 uint rot = (tmp >> (I.sizeof * 8 - constants.opBits)) & constants.mask; 193 } else { 194 uint rot = 0; 195 } 196 uint amprot = (rot << constants.amplifier) & constants.mask; 197 return rotr(xorshift!X(tmp, constants.xShift, constants.bottomSpare), amprot); 198 } 199 200 struct PCG(T, S, alias func, S multiplier = DefaultPCGMultiplier!S, S increment = DefaultPCGIncrement!S) { 201 private S state; 202 203 this(S val) @safe pure nothrow @nogc { 204 seed(val); 205 } 206 void seed(S val) @safe pure nothrow @nogc { 207 state = cast(S)(val + increment); 208 popFront(); 209 } 210 void popFront() @safe pure nothrow @nogc { 211 state = cast(S)(state * multiplier + increment); 212 } 213 T front() const @safe pure nothrow @nogc @property { 214 return func!T(state); 215 } 216 typeof(this) save() @safe pure nothrow @nogc { 217 return this; 218 } 219 enum bool empty = false; 220 enum bool isUniformRandom = true; 221 enum T min = T.min; 222 enum T max = T.max; 223 const(S) toSiryulType()() const @safe { 224 return state; 225 } 226 static PCG fromSiryulType()(S val) @safe { 227 PCG result; 228 result.state = val; 229 return result; 230 } 231 } 232 233 template DefaultPCGMultiplier(T) if (isIntegral!T) { 234 static if (is(T == ubyte)) { 235 enum DefaultPCGMultiplier = 141; 236 } else static if (is(T == ushort)) { 237 enum DefaultPCGMultiplier = 12829; 238 } else static if (is(T == uint)) { 239 enum DefaultPCGMultiplier = 747796405; 240 } else static if (is(T == ulong)) { 241 enum DefaultPCGMultiplier = 6364136223846793005; 242 } else static if (is(T == ucent)) { 243 //enum DefaultPCGMultiplier = 47026247687942121848144207491837523525; 244 } 245 } 246 247 template DefaultPCGIncrement(T) if (isIntegral!T) { 248 static if (is(T == ubyte)) { 249 enum DefaultPCGIncrement = 77; 250 } else static if (is(T == ushort)) { 251 enum DefaultPCGIncrement = 47989; 252 } else static if (is(T == uint)) { 253 enum DefaultPCGIncrement = 2891336453; 254 } else static if (is(T == ulong)) { 255 enum DefaultPCGIncrement = 1442695040888963407; 256 } else static if (is(T == ucent)) { 257 //enum DefaultPCGIncrement = 117397592171526113268558934119004209487; 258 } 259 } 260 261 private template PCGMMultiplier(T) if (isIntegral!T) { 262 static if (is(T : ubyte)) { 263 enum PCGMMultiplier = 217; 264 } else static if (is(T : ushort)) { 265 enum PCGMMultiplier = 62169; 266 } else static if (is(T : uint)) { 267 enum PCGMMultiplier = 277803737; 268 } else static if (is(T : ulong)) { 269 enum PCGMMultiplier = 12605985483714917081; 270 //} else static if (is(T == ucent)) { 271 //enum PCGMMultiplier = 327738287884841127335028083622016905945; 272 } 273 } 274 275 alias SupportedTypes = AliasSeq!(ubyte, ushort, uint, ulong); 276 alias SupportedFunctions = AliasSeq!(xshrr, xshrs, xsh, xsl, rxs, rxsmxs, rxsm, xslrr); 277 278 import std.conv : text; 279 static foreach (ResultType; SupportedTypes) { 280 static foreach (StateType; SupportedTypes) { 281 static if (StateType.sizeof >= ResultType.sizeof) { 282 static foreach (Function; SupportedFunctions) { 283 mixin("alias PCG", text(StateType.sizeof * 8, ResultType.sizeof * 8, __traits(identifier, Function)), " = PCG!(ResultType, StateType, Function);"); 284 } 285 } 286 } 287 } 288 alias PCG6432dxsm = PCG!(uint, ulong, dxsm); 289 290 @safe unittest { 291 import std.algorithm : reduce; 292 import std.datetime.stopwatch : benchmark; 293 import std.math : pow, sqrt; 294 import std.random : isSeedable, Mt19937, uniform, uniform01, unpredictableSeed; 295 import std.stdio : writefln, writeln; 296 auto seed = unpredictableSeed; 297 298 void testRNG(RNG, string name)(uint seed) { 299 static if (isSeedable!(RNG, uint)) { 300 auto rng = RNG(seed); 301 } else static if (isSeedable!(RNG, ushort)) { 302 auto rng = RNG(cast(ushort)seed); 303 } else static if (isSeedable!(RNG, ubyte)) { 304 auto rng = RNG(cast(ubyte)seed); 305 } 306 writefln!"--%s--"(name); 307 double total = 0; 308 ulong[ubyte] distribution; 309 void test() { 310 total += uniform01(rng); 311 distribution.require(uniform!ubyte(rng), 0)++; 312 } 313 auto result = benchmark!(test)(1000000)[0]; 314 writefln!"Benchmark completed in %s"(result); 315 writeln(total); 316 double avg = reduce!((a, b) => a + b / distribution.length)(0.0f, distribution); 317 auto var = reduce!((a, b) => a + pow(b - avg, 2) / distribution.length)(0.0f, distribution); 318 auto sd = sqrt(var); 319 writefln!"Average: %s, Standard deviation: %s"(avg, sd); 320 } 321 322 testRNG!(PCG168xshrr, "PCG168xshrr")(seed); 323 testRNG!(PCG3216xshrr, "PCG3216xshrr")(seed); 324 testRNG!(PCG6432xslrr, "PCG6432xslrr")(seed); 325 testRNG!(PCG648rxsmxs, "PCG648rxsmxs")(seed); 326 testRNG!(PCG6432dxsm, "PCG6432dxsm")(seed); 327 testRNG!(Mt19937, "Mt19937")(seed); 328 }