1 /** 2 Random Number Generation 3 4 This module contains the base interface for random number generators, 5 as well as a Mersenne Twister random number generator. 6 7 Copyright: 8 Copyright © 2023-2025, Kitsunebi Games 9 Copyright © 2023-2025, Inochi2D Project 10 11 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 12 Authors: Luna Nielsen 13 */ 14 module nulib.random; 15 import numem; 16 17 /** 18 Base class of all random number generators 19 */ 20 abstract 21 class RandomBase { 22 @nogc nothrow: 23 24 /** 25 Resets the random number generator to its initial state. 26 */ 27 abstract void reset(); 28 29 /** 30 Gets the next value in the random stream 31 */ 32 abstract size_t next(); 33 34 /** 35 Gets the next value in the random stream 36 */ 37 abstract double nextUniform(); 38 39 /** 40 Gets the next bytes in the random stream 41 */ 42 abstract void next(ref ubyte[] destination); 43 } 44 45 /** 46 A psuedo-random number generator 47 */ 48 class Random : RandomBase { 49 @nogc nothrow: 50 private: 51 52 enum mtWordSize = (size_t.sizeof*8); 53 static if (mtWordSize == 32) { 54 enum mtRecurrence = 624; 55 enum mtMiddleWord = 397; 56 enum mtSeperationPoint = 31; 57 enum mtMagicNumber = 1_812_433_253; 58 enum mtTwistCoefficient = 0x9908B0DF; 59 60 enum mtU = 11; 61 enum mtD = 0xFFFFFFFF; 62 enum mtS = 7; 63 enum mtB = 0x9D2C5680; 64 enum mtT = 15; 65 enum mtC = 0xEFC60000; 66 enum mtL = 18; 67 68 } else { 69 enum mtRecurrence = 312; 70 enum mtMiddleWord = 156; 71 enum mtSeperationPoint = 31; 72 enum mtMagicNumber = 6_364_136_223_846_793_005; 73 enum mtTwistCoefficient = 0xB5026F5AA96619E9; 74 75 enum mtU = 29; 76 enum mtD = 0x5555555555555555; 77 enum mtS = 17; 78 enum mtB = 0x71D67FFFEDA60000; 79 enum mtT = 37; 80 enum mtC = 0xFFF7EEE000000000; 81 enum mtL = 43; 82 } 83 84 enum mtUpperMask = (size_t.max << mtSeperationPoint); 85 enum mtLowerMask = (size_t.max >> (mtWordSize-mtSeperationPoint)); 86 87 88 size_t[mtRecurrence] state; 89 size_t idx; 90 size_t seed; 91 92 /// Resets the mersenne twister algorithm. 93 void initialize() { 94 state[0] = seed; 95 96 // NOTE: Local seed iteration. 97 // This is to allow calling reset() 98 size_t nseed = seed; 99 foreach(i; 1..state.length) { 100 nseed = mtMagicNumber * (nseed ^ (nseed >> (mtWordSize-2))) + i; 101 state[i] = nseed; 102 } 103 104 idx = 0; 105 } 106 107 public: 108 109 /** 110 Constructs a random number generator with a set seed. 111 */ 112 this(size_t seed) { 113 this.seed = seed; 114 this.initialize(); 115 } 116 117 /** 118 Constructs a random number generator with a set seed. 119 */ 120 override 121 void reset() { 122 this.initialize(); 123 } 124 125 /** 126 Gets the next value in the random stream 127 */ 128 override 129 size_t next() { 130 131 // Get current index and at the opposite end of the circular buffer. 132 ptrdiff_t k = idx; 133 ptrdiff_t j = k - (mtRecurrence-1); 134 if (j < 0) 135 j += mtRecurrence; 136 137 size_t x = (state[k] & mtUpperMask) | (state[j] & mtLowerMask); 138 139 140 size_t xA = x >> 1; 141 if (x & 1) xA ^= mtTwistCoefficient; 142 143 // Point to state recurrange - magic number 144 // modulo if need be. 145 j = k - (mtRecurrence-mtMiddleWord); 146 if (j < 0) 147 j += mtRecurrence; 148 149 // Compute and set next state value 150 x = state[j] ^ xA; 151 state[k++] = x; 152 153 // Wrap around 154 if (k >= mtRecurrence) 155 k = 0; 156 idx = k; 157 158 // Tempering algorithm 159 size_t y = x ^ (x >> mtU); 160 y = y ^ ((y << mtS) & mtB); 161 y = y ^ ((y << mtT) & mtC); 162 163 return y ^ (y >> 1); 164 } 165 166 /** 167 Gets the next value in the random stream 168 */ 169 override 170 double nextUniform() { 171 size_t nrandom = this.next(); 172 return cast(double)nrandom/cast(double)size_t.max; 173 } 174 175 /** 176 Gets the next bytes in the random stream 177 */ 178 override 179 void next(ref ubyte[] destination) { 180 181 // State of random number generator 182 union tmp { 183 ubyte[size_t.sizeof] buffer; 184 size_t nrandom; 185 } 186 tmp _tmp; 187 188 // Algorithm for filling buffer 189 size_t i = 0; 190 while (i < destination.length) { 191 _tmp.nrandom = next(); 192 193 // Figre out how many bytes to copy over 194 size_t count = size_t.sizeof; 195 if (i+size_t.sizeof > destination.length) 196 count = (i+size_t.sizeof) - destination.length; 197 198 // Write to destination 199 destination[i..i+count] = _tmp.buffer[0..count]; 200 i += count; 201 } 202 } 203 } 204 205 @("Random") 206 unittest { 207 // Mersenne Twister is deterministic, so this should always give the same result 208 // (on 64-bit systems) 209 static if (size_t.sizeof == 8) { 210 const ubyte[128] verification = [ 211 155, 16, 179, 137, 163, 208, 254, 167, 182, 56, 19, 183, 127, 46, 212 4, 165, 24, 95, 218, 132, 62, 197, 42, 151, 145, 200, 45, 143, 88, 213 166, 19, 52, 93, 142, 195, 160, 49, 12, 35, 123, 216, 164, 13, 106, 214 204, 45, 231, 157, 109, 165, 89, 86, 236, 142, 4, 245, 84, 188, 235, 215 162, 184, 89, 247, 17, 72, 92, 160, 219, 113, 83, 43, 44, 180, 191, 216 109, 53, 245, 47, 125, 196, 234, 11, 19, 92, 185, 161, 167, 76, 114, 217 113, 255, 64, 83, 191, 254, 194, 169, 61, 243, 22, 54, 232, 196, 110, 218 82, 208, 110, 80, 6, 188, 68, 101, 60, 116, 47, 210, 79, 186, 138, 122, 219 205, 191, 62, 59, 194, 137, 117, 5 220 ]; 221 222 ubyte[128] buffer; 223 ubyte[] dest = buffer[0..$]; 224 225 Random random = nogc_new!Random(42); 226 random.next(dest); 227 228 assert(dest == verification[0..$]); 229 } 230 231 // Test skipped on 32 bit. 232 } 233 234 @("Random (seed reset)") 235 unittest { 236 237 ubyte[128] buffer1; 238 ubyte[] dest1 = buffer1[0..$]; 239 240 ubyte[128] buffer2; 241 ubyte[] dest2 = buffer2[0..$]; 242 243 Random random = nogc_new!Random(42); 244 245 // These should not match 246 random.next(dest1); 247 random.next(dest2); 248 assert(dest1 != dest2, "Randomness is diminished?"); 249 250 251 // These should not match 252 random.reset(); 253 random.next(dest1); 254 random.reset(); 255 random.next(dest2); 256 257 assert(dest1 == dest2, "Randomness is not deterministic?"); 258 } 259 260 // TODO: optional crypto rng?