xref: /PHP-8.0/ext/standard/mt_rand.c (revision 2b5de6f8)
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | http://www.php.net/license/3_01.txt                                  |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Authors: Rasmus Lerdorf <rasmus@php.net>                             |
14    |          Zeev Suraski <zeev@php.net>                                 |
15    |          Pedro Melo <melo@ip.pt>                                     |
16    |          Sterling Hughes <sterling@php.net>                          |
17    |                                                                      |
18    | Based on code from: Richard J. Wagner <rjwagner@writeme.com>         |
19    |                     Makoto Matsumoto <matumoto@math.keio.ac.jp>      |
20    |                     Takuji Nishimura                                 |
21    |                     Shawn Cokus <Cokus@math.washington.edu>          |
22    +----------------------------------------------------------------------+
23  */
24 
25 #include "php.h"
26 #include "php_rand.h"
27 #include "php_mt_rand.h"
28 
29 /* MT RAND FUNCTIONS */
30 
31 /*
32 	The following php_mt_...() functions are based on a C++ class MTRand by
33 	Richard J. Wagner. For more information see the web page at
34 	http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/VERSIONS/C-LANG/MersenneTwister.h
35 
36 	Mersenne Twister random number generator -- a C++ class MTRand
37 	Based on code by Makoto Matsumoto, Takuji Nishimura, and Shawn Cokus
38 	Richard J. Wagner  v1.0  15 May 2003  rjwagner@writeme.com
39 
40 	The Mersenne Twister is an algorithm for generating random numbers.  It
41 	was designed with consideration of the flaws in various other generators.
42 	The period, 2^19937-1, and the order of equidistribution, 623 dimensions,
43 	are far greater.  The generator is also fast; it avoids multiplication and
44 	division, and it benefits from caches and pipelines.  For more information
45 	see the inventors' web page at http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
46 
47 	Reference
48 	M. Matsumoto and T. Nishimura, "Mersenne Twister: A 623-Dimensionally
49 	Equidistributed Uniform Pseudo-Random Number Generator", ACM Transactions on
50 	Modeling and Computer Simulation, Vol. 8, No. 1, January 1998, pp 3-30.
51 
52 	Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
53 	Copyright (C) 2000 - 2003, Richard J. Wagner
54 	All rights reserved.
55 
56 	Redistribution and use in source and binary forms, with or without
57 	modification, are permitted provided that the following conditions
58 	are met:
59 
60 	1. Redistributions of source code must retain the above copyright
61 	   notice, this list of conditions and the following disclaimer.
62 
63 	2. Redistributions in binary form must reproduce the above copyright
64 	   notice, this list of conditions and the following disclaimer in the
65 	   documentation and/or other materials provided with the distribution.
66 
67 	3. The names of its contributors may not be used to endorse or promote
68 	   products derived from this software without specific prior written
69 	   permission.
70 
71 	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
72 	"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
73 	LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
74 	A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
75 	CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
76 	EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
77 	PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
78 	PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
79 	LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
80 	NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
81 	SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
82 */
83 
84 #define N             MT_N                 /* length of state vector */
85 #define M             (397)                /* a period parameter */
86 #define hiBit(u)      ((u) & 0x80000000U)  /* mask all but highest   bit of u */
87 #define loBit(u)      ((u) & 0x00000001U)  /* mask all but lowest    bit of u */
88 #define loBits(u)     ((u) & 0x7FFFFFFFU)  /* mask     the highest   bit of u */
89 #define mixBits(u, v) (hiBit(u)|loBits(v)) /* move hi bit of u to hi bit of v */
90 
91 #define twist(m,u,v)  (m ^ (mixBits(u,v)>>1) ^ ((uint32_t)(-(int32_t)(loBit(v))) & 0x9908b0dfU))
92 #define twist_php(m,u,v)  (m ^ (mixBits(u,v)>>1) ^ ((uint32_t)(-(int32_t)(loBit(u))) & 0x9908b0dfU))
93 
94 /* {{{ php_mt_initialize */
php_mt_initialize(uint32_t seed,uint32_t * state)95 static inline void php_mt_initialize(uint32_t seed, uint32_t *state)
96 {
97 	/* Initialize generator state with seed
98 	   See Knuth TAOCP Vol 2, 3rd Ed, p.106 for multiplier.
99 	   In previous versions, most significant bits (MSBs) of the seed affect
100 	   only MSBs of the state array.  Modified 9 Jan 2002 by Makoto Matsumoto. */
101 
102 	register uint32_t *s = state;
103 	register uint32_t *r = state;
104 	register int i = 1;
105 
106 	*s++ = seed & 0xffffffffU;
107 	for( ; i < N; ++i ) {
108 		*s++ = ( 1812433253U * ( *r ^ (*r >> 30) ) + i ) & 0xffffffffU;
109 		r++;
110 	}
111 }
112 /* }}} */
113 
114 /* {{{ php_mt_reload */
php_mt_reload(void)115 static inline void php_mt_reload(void)
116 {
117 	/* Generate N new values in state
118 	   Made clearer and faster by Matthew Bellew (matthew.bellew@home.com) */
119 
120 	register uint32_t *state = BG(state);
121 	register uint32_t *p = state;
122 	register int i;
123 
124 	if (BG(mt_rand_mode) == MT_RAND_MT19937) {
125 		for (i = N - M; i--; ++p)
126 			*p = twist(p[M], p[0], p[1]);
127 		for (i = M; --i; ++p)
128 			*p = twist(p[M-N], p[0], p[1]);
129 		*p = twist(p[M-N], p[0], state[0]);
130 	}
131 	else {
132 		for (i = N - M; i--; ++p)
133 			*p = twist_php(p[M], p[0], p[1]);
134 		for (i = M; --i; ++p)
135 			*p = twist_php(p[M-N], p[0], p[1]);
136 		*p = twist_php(p[M-N], p[0], state[0]);
137 	}
138 	BG(left) = N;
139 	BG(next) = state;
140 }
141 /* }}} */
142 
143 /* {{{ php_mt_srand */
php_mt_srand(uint32_t seed)144 PHPAPI void php_mt_srand(uint32_t seed)
145 {
146 	/* Seed the generator with a simple uint32 */
147 	php_mt_initialize(seed, BG(state));
148 	php_mt_reload();
149 
150 	/* Seed only once */
151 	BG(mt_rand_is_seeded) = 1;
152 }
153 /* }}} */
154 
155 /* {{{ php_mt_rand */
php_mt_rand(void)156 PHPAPI uint32_t php_mt_rand(void)
157 {
158 	/* Pull a 32-bit integer from the generator state
159 	   Every other access function simply transforms the numbers extracted here */
160 
161 	register uint32_t s1;
162 
163 	if (UNEXPECTED(!BG(mt_rand_is_seeded))) {
164 		php_mt_srand(GENERATE_SEED());
165 	}
166 
167 	if (BG(left) == 0) {
168 		php_mt_reload();
169 	}
170 	--BG(left);
171 
172 	s1 = *BG(next)++;
173 	s1 ^= (s1 >> 11);
174 	s1 ^= (s1 <<  7) & 0x9d2c5680U;
175 	s1 ^= (s1 << 15) & 0xefc60000U;
176 	return ( s1 ^ (s1 >> 18) );
177 }
178 /* }}} */
179 
180 /* {{{ Seeds Mersenne Twister random number generator */
PHP_FUNCTION(mt_srand)181 PHP_FUNCTION(mt_srand)
182 {
183 	zend_long seed = 0;
184 	zend_long mode = MT_RAND_MT19937;
185 
186 	ZEND_PARSE_PARAMETERS_START(0, 2)
187 		Z_PARAM_OPTIONAL
188 		Z_PARAM_LONG(seed)
189 		Z_PARAM_LONG(mode)
190 	ZEND_PARSE_PARAMETERS_END();
191 
192 	if (ZEND_NUM_ARGS() == 0)
193 		seed = GENERATE_SEED();
194 
195 	switch (mode) {
196 		case MT_RAND_PHP:
197 			BG(mt_rand_mode) = MT_RAND_PHP;
198 			break;
199 		default:
200 			BG(mt_rand_mode) = MT_RAND_MT19937;
201 	}
202 
203 	php_mt_srand(seed);
204 }
205 /* }}} */
206 
rand_range32(uint32_t umax)207 static uint32_t rand_range32(uint32_t umax) {
208 	uint32_t result, limit;
209 
210 	result = php_mt_rand();
211 
212 	/* Special case where no modulus is required */
213 	if (UNEXPECTED(umax == UINT32_MAX)) {
214 		return result;
215 	}
216 
217 	/* Increment the max so the range is inclusive of max */
218 	umax++;
219 
220 	/* Powers of two are not biased */
221 	if ((umax & (umax - 1)) == 0) {
222 		return result & (umax - 1);
223 	}
224 
225 	/* Ceiling under which UINT32_MAX % max == 0 */
226 	limit = UINT32_MAX - (UINT32_MAX % umax) - 1;
227 
228 	/* Discard numbers over the limit to avoid modulo bias */
229 	while (UNEXPECTED(result > limit)) {
230 		result = php_mt_rand();
231 	}
232 
233 	return result % umax;
234 }
235 
236 #if ZEND_ULONG_MAX > UINT32_MAX
rand_range64(uint64_t umax)237 static uint64_t rand_range64(uint64_t umax) {
238 	uint64_t result, limit;
239 
240 	result = php_mt_rand();
241 	result = (result << 32) | php_mt_rand();
242 
243 	/* Special case where no modulus is required */
244 	if (UNEXPECTED(umax == UINT64_MAX)) {
245 		return result;
246 	}
247 
248 	/* Increment the max so the range is inclusive of max */
249 	umax++;
250 
251 	/* Powers of two are not biased */
252 	if ((umax & (umax - 1)) == 0) {
253 		return result & (umax - 1);
254 	}
255 
256 	/* Ceiling under which UINT64_MAX % max == 0 */
257 	limit = UINT64_MAX - (UINT64_MAX % umax) - 1;
258 
259 	/* Discard numbers over the limit to avoid modulo bias */
260 	while (UNEXPECTED(result > limit)) {
261 		result = php_mt_rand();
262 		result = (result << 32) | php_mt_rand();
263 	}
264 
265 	return result % umax;
266 }
267 #endif
268 
269 /* {{{ php_mt_rand_range */
php_mt_rand_range(zend_long min,zend_long max)270 PHPAPI zend_long php_mt_rand_range(zend_long min, zend_long max)
271 {
272 	zend_ulong umax = max - min;
273 
274 #if ZEND_ULONG_MAX > UINT32_MAX
275 	if (umax > UINT32_MAX) {
276 		return (zend_long) (rand_range64(umax) + min);
277 	}
278 #endif
279 
280 	return (zend_long) (rand_range32(umax) + min);
281 }
282 /* }}} */
283 
284 /* {{{ php_mt_rand_common
285  * rand() allows min > max, mt_rand does not */
php_mt_rand_common(zend_long min,zend_long max)286 PHPAPI zend_long php_mt_rand_common(zend_long min, zend_long max)
287 {
288 	int64_t n;
289 
290 	if (BG(mt_rand_mode) == MT_RAND_MT19937) {
291 		return php_mt_rand_range(min, max);
292 	}
293 
294 	/* Legacy mode deliberately not inside php_mt_rand_range()
295 	 * to prevent other functions being affected */
296 	n = (int64_t)php_mt_rand() >> 1;
297 	RAND_RANGE_BADSCALING(n, min, max, PHP_MT_RAND_MAX);
298 
299 	return n;
300 }
301 /* }}} */
302 
303 /* {{{ Returns a random number from Mersenne Twister */
PHP_FUNCTION(mt_rand)304 PHP_FUNCTION(mt_rand)
305 {
306 	zend_long min;
307 	zend_long max;
308 	int argc = ZEND_NUM_ARGS();
309 
310 	if (argc == 0) {
311 		// genrand_int31 in mt19937ar.c performs a right shift
312 		RETURN_LONG(php_mt_rand() >> 1);
313 	}
314 
315 	ZEND_PARSE_PARAMETERS_START(2, 2)
316 		Z_PARAM_LONG(min)
317 		Z_PARAM_LONG(max)
318 	ZEND_PARSE_PARAMETERS_END();
319 
320 	if (UNEXPECTED(max < min)) {
321 		zend_argument_value_error(2, "must be greater than or equal to argument #1 ($min)");
322 		RETURN_THROWS();
323 	}
324 
325 	RETURN_LONG(php_mt_rand_common(min, max));
326 }
327 /* }}} */
328 
329 /* {{{ Returns the maximum value a random number from Mersenne Twister can have */
PHP_FUNCTION(mt_getrandmax)330 PHP_FUNCTION(mt_getrandmax)
331 {
332 	ZEND_PARSE_PARAMETERS_NONE();
333 
334 	/*
335 	 * Melo: it could be 2^^32 but we only use 2^^31 to maintain
336 	 * compatibility with the previous php_rand
337 	 */
338   	RETURN_LONG(PHP_MT_RAND_MAX); /* 2^^31 */
339 }
340 /* }}} */
341 
PHP_MINIT_FUNCTION(mt_rand)342 PHP_MINIT_FUNCTION(mt_rand)
343 {
344 	REGISTER_LONG_CONSTANT("MT_RAND_MT19937", MT_RAND_MT19937, CONST_CS | CONST_PERSISTENT);
345 	REGISTER_LONG_CONSTANT("MT_RAND_PHP",     MT_RAND_PHP, CONST_CS | CONST_PERSISTENT);
346 
347 	return SUCCESS;
348 }
349