php-src/ext/standard/rand.c
Jeroen van Wolffelaar 92f1248a49 Update credits
2001-09-03 01:09:35 +00:00

315 lines
9.4 KiB
C

/*
+----------------------------------------------------------------------+
| PHP version 4.0 |
+----------------------------------------------------------------------+
| Copyright (c) 1997-2001 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 2.02 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available at through the world-wide-web at |
| http://www.php.net/license/2_02.txt. |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Authors: Rasmus Lerdorf <rasmus@lerdorf.on.ca> |
| Zeev Suraski <zeev@zend.com> |
| Pedro Melo <melo@ip.pt> |
| Jeroen van Wolffelaar <jeroen@php.net> |
+----------------------------------------------------------------------+
*/
/* $Id$ */
#include <stdlib.h>
#include "php.h"
#include "php_math.h"
#include "php_rand.h"
#include "php_ini.h"
#include "zend_execute.h"
#include "basic_functions.h"
/* See php_rand.h for information about layout */
/* an ARRAY of POINTERS, not vice versa */
php_randgen_entry *php_randgen_entries[PHP_RAND_NUMRANDS];
#define PHP_HAS_SRAND(which) (php_randgen_entries[which]->srand)
#define PHP_SRAND(which,seed) ((*(php_randgen_entries[which]->srand))(seed))
#define PHP_RAND(which) ((*(php_randgen_entries[which]->rand))())
#define PHP_RANDMAX(which) (php_randgen_entries[which]->randmax)
#define PHP_RAND_INISTR(which) (php_randgen_entries[which]->ini_str)
#define CURR_GEN BG(rand_generator_current)
PHP_MINIT_FUNCTION(rand)
{
PHP_MINIT(rand_sys)(INIT_FUNC_ARGS_PASSTHRU);
PHP_MINIT(rand_mt)(INIT_FUNC_ARGS_PASSTHRU);
}
/* TODO: check that this function is called on the start of each script
* execution: not more often, not less often.
*
* Note that system rand is inherently thread-unsafe: A different thread can
* always eat up some rand()'s, and thus nuking your expected sequence.
* Another reason to use MT...
*/
PHP_RINIT_FUNCTION(rand)
{
register int i;
/* seed all number-generators */
/* FIXME: or seed relevant numgen on init/update ini-entry? */
for (i = 0 ; i < PHP_RAND_NUMRANDS ; i++) {
if (PHP_HAS_SRAND(i)) {
#define SRAND_A_RANDOM_SEED (time(0) * getpid() * (php_combined_lcg(TSRMLS_C) * 10000.0)) /* something with microtime? */
PHP_SRAND(i,SRAND_A_RANDOM_SEED);
}
}
}
/* INI */
static int randgen_str_to_int(char *str, int strlen)
{
register int i;
for (i = 0 ; i < PHP_RAND_NUMRANDS ; i++) {
if (!strcasecmp(str, PHP_RAND_INISTR(i)))
return i;
}
return -1;
}
/* FIXME: check that this is called on initial ini-parsing too */
/* FIXME: what if no ini-entry was present? */
static PHP_INI_MH(OnUpdateRandGen)
{
/* Set BG(rand_generator) to the correct integer value indicating
* ini-setting */
BG(rand_generator) = randgen_str_to_int(new_value, new_value_length);
if (BG(rand_generator) == -1) {
/* FIXME: is this possible? What happens if this occurs during
* ini-parsing at startup? */
php_error(E_WARNING,"Invalid value for random_number_generator: \"%s\"", new_value);
/* Fallback: */
BG(rand_generator) = PHP_RAND_DEFAULT;
}
#ifdef DEBUG_RAND
printf("\nRAND-INI updated: %d\n",BG(rand_generator));
#endif
return SUCCESS;
}
PHP_INI_BEGIN()
/* FIXME: default is hardcoded here, this is the second place */
PHP_INI_ENTRY("random_number_generator", "mt", PHP_INI_ALL, OnUpdateRandGen)
PHP_INI_END()
/* srand */
/* {{{ PHPAPI void php_srand(void) */
PHPAPI void php_srand(void)
{
CURR_GEN = BG(rand_generator);
PHP_SRAND(BG(rand_generator), SRAND_A_RANDOM_SEED);
}
/* }}} */
/* {{{ [mt_]srand common */
#define pim_srand_common(name,type) \
PHP_FUNCTION(name) \
{ \
zval **seed; \
zval **alg; \
\
switch (ZEND_NUM_ARGS()) { \
case 0: \
CURR_GEN = BG(rand_generator); \
PHP_SRAND(BG(rand_generator), SRAND_A_RANDOM_SEED); \
RETURN_TRUE; \
case 1: \
zend_get_parameters_ex(1, &seed); \
convert_to_long_ex(seed); \
CURR_GEN = type; \
PHP_SRAND(type, Z_LVAL_PP(seed)); \
RETURN_TRUE; \
case 2: \
/* algorithm, seed is most logic, though it is the other way
* around than current way... */ \
zend_get_parameters_ex(2, &alg, &seed); \
convert_to_long_ex(seed); \
convert_to_long_ex(alg); \
if (0 > Z_LVAL_PP(alg) || Z_LVAL_PP(alg) >= PHP_RAND_NUMRANDS) { \
php_error(E_WARNING, "%s(): There is no algorithm %d.", get_active_function_name(TSRMLS_C), Z_LVAL_PP(alg)); \
RETURN_FALSE; \
} \
if (!PHP_HAS_SRAND(Z_LVAL_PP(alg))) { \
php_error(E_WARNING, "%s(): Algorithm %d does not support reproducable results.", get_active_function_name(TSRMLS_C), Z_LVAL_PP(alg)); \
RETURN_FALSE; \
} \
CURR_GEN = Z_LVAL_PP(alg); \
PHP_SRAND(Z_LVAL_PP(alg), Z_LVAL_PP(seed)); \
RETURN_TRUE; \
default: \
WRONG_PARAM_COUNT; \
} \
}
/* }}} */
/* {{{ proto bool srand(int seed)
Seeds random number generator */
pim_srand_common(srand,PHP_RAND_SYS)
/* }}} */
/* {{{ proto bool mt_srand(int seed)
Seeds random number generator */
pim_srand_common(mt_srand,PHP_RAND_MT)
/* }}} */
/* rand */
/* {{{ PHPAPI long php_rand(void) */
PHPAPI long php_rand(void)
{
return PHP_RAND(CURR_GEN);
}
/* }}} */
/* {{{ PHPAPI double php_drand(void)
* returns a double in the range [0,1) */
PHPAPI double php_drand(void)
{
return (double)php_rand() /
(double)(PHP_RANDMAX(CURR_GEN)+1.0);
}
/* }}} */
/* {{{ macro: PHP_RAND_RANGE */
#define PHP_RAND_RANGE(which,min,max,result) { \
/*
* A bit of tricky math here. We want to avoid using a modulus because
* that simply tosses the high-order bits and might skew the distribution
* of random values over the range. Instead we map the range directly.
*
* We need to map the range from 0...M evenly to the range a...b
* Expressed in real numbers, this becomes:
*
* [0,M+1[ mapped to [a,b+1[
*
* Let n = the random number and n' = the mapped random number
* So the formula needs to be:
*
* n' = a + n((b+1)-a)/(m+1)
*
* This isn't perfect, because n only takes integer values. So when a..b
* spans a significant portion of 0..M, some numbers have nearly twice as
* much chance. But since twice a very small chance is still a very small
* chance, it's ignored.
*
* --Rasmus and Jeroen
*/ \
(result) = PHP_RAND(which); \
if ((max) < (min)) { \
php_error(E_WARNING, "%s(): Invalid range: %ld..%ld (minimum can't be larger than maximum)", \
get_active_function_name(TSRMLS_C), (min), (max)); \
} else if ( (max) - (min) > PHP_RANDMAX(which) ) { \
/* TODO: this can done better, get two numbers and combine... */ \
php_error(E_WARNING, "%s(): Invalid range: %ld..%ld (can't give that much randomness)", \
get_active_function_name(TSRMLS_C), (min), (max)); \
} \
(result) = (min) + (long) ((double)((max)-(min)+1) * (result)/(PHP_RANDMAX(which)+1.0)); \
}
/* }}} */
/* {{{ PHPAPI long php_rand_range(long min, long max) */
PHPAPI long php_rand_range(long min, long max)
{
register long result;
PHP_RAND_RANGE(CURR_GEN, min, max, result);
return result;
}
/* }}} */
/* {{{ [mt_]rand common */
#define PHP_FUNCTION_RAND(name,which) \
PHP_FUNCTION(name) \
{ \
zval **min, **max; \
\
switch (ZEND_NUM_ARGS()) { \
case 0: \
RETURN_LONG(PHP_RAND(which)); \
case 2: \
if (zend_get_parameters_ex(2, &min, &max)==FAILURE) { \
RETURN_FALSE; \
} \
convert_to_long_ex(min); \
convert_to_long_ex(max); \
Z_TYPE_P(return_value) = IS_LONG; \
PHP_RAND_RANGE(which, Z_LVAL_PP(min), \
Z_LVAL_PP(max), Z_LVAL_P(return_value)); \
return; \
default: \
WRONG_PARAM_COUNT; \
break; \
} \
}
/* }}} */
/* {{{ proto int rand([int min, int max])
Returns a random number */
PHP_FUNCTION_RAND(rand,PHP_RAND_SYS)
/* }}} */
/* {{{ proto int mt_rand([int min, int max])
Returns a random number by means of Mersenne Twister */
PHP_FUNCTION_RAND(mt_rand,PHP_RAND_MT)
/* }}} */
/* getrandmax */
/* {{{ PHPAPI long php_randmax(void)
Returns the maximum value a random number can have */
PHPAPI long php_randmax(void)
{
return PHP_RANDMAX(CURR_GEN);
}
/* }}} */
/* {{{ proto int getrandmax(void)
Returns the maximum value a random number can have */
PHP_FUNCTION(getrandmax)
{
if (ZEND_NUM_ARGS() != 0) {
WRONG_PARAM_COUNT;
}
RETURN_LONG( php_randmax());
}
/* }}} */
/* {{{ proto int mt_getrandmax(void)
Returns the maximum value a random number can have */
PHP_FUNCTION(mt_getrandmax)
{
if (ZEND_NUM_ARGS() != 0) {
WRONG_PARAM_COUNT;
}
RETURN_LONG( php_randmax() );
}
/* }}} */
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: sw=4 ts=4 tw=78 fdm=marker
* vim: sw=4 ts=4 tw=78
*/