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 }