Add Randomizer::nextFloat() and Randomizer::getFloat() (#9679)

* random: Add Randomizer::nextFloat()

* random: Check that doubles are IEEE-754 in Randomizer::nextFloat()

* random: Add Randomizer::nextFloat() tests

* random: Add Randomizer::getFloat() implementing the y-section algorithm

The algorithm is published in:

Drawing Random Floating-Point Numbers from an Interval. Frédéric
Goualard, ACM Trans. Model. Comput. Simul., 32:3, 2022.
https://doi.org/10.1145/3503512

* random: Implement getFloat_gamma() optimization

see https://github.com/php/php-src/pull/9679/files#r994668327

* random: Add Random\IntervalBoundary

* random: Split the implementation of γ-section into its own file

* random: Add tests for Randomizer::getFloat()

* random: Fix γ-section for 32-bit systems

* random: Replace check for __STDC_IEC_559__ by compile-time check for DBL_MANT_DIG

* random: Drop nextFloat_spacing.phpt

* random: Optimize Randomizer::getFloat() implementation

* random: Reject non-finite parameters in Randomizer::getFloat()

* random: Add NEWS/UPGRADING for Randomizer’s float functionality
This commit is contained in:
Tim Düsterhus 2022-12-14 17:48:47 +01:00 committed by GitHub
parent 284f61ee22
commit f9a1a90380
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 511 additions and 6 deletions

1
NEWS
View file

@ -60,6 +60,7 @@ PHP NEWS
- Random: - Random:
. Added Randomizer::getBytesFromString(). (Joshua Rüsweg) . Added Randomizer::getBytesFromString(). (Joshua Rüsweg)
. Added Randomizer::nextFloat(), ::getFloat(), and IntervalBoundary. (timwolla)
- Reflection: - Reflection:
. Fix GH-9470 (ReflectionMethod constructor should not find private parent . Fix GH-9470 (ReflectionMethod constructor should not find private parent

View file

@ -66,6 +66,8 @@ PHP 8.3 UPGRADE NOTES
- Random: - Random:
. Added Randomizer::getBytesFromString(). . Added Randomizer::getBytesFromString().
RFC: https://wiki.php.net/rfc/randomizer_additions RFC: https://wiki.php.net/rfc/randomizer_additions
. Added Randomizer::nextFloat(), ::getFloat(), and IntervalBoundary.
RFC: https://wiki.php.net/rfc/randomizer_additions
- Sockets: - Sockets:
. Added socket_atmark to checks if the socket is OOB marked. . Added socket_atmark to checks if the socket is OOB marked.

View file

@ -25,6 +25,7 @@ PHP_NEW_EXTENSION(random,
engine_xoshiro256starstar.c \ engine_xoshiro256starstar.c \
engine_secure.c \ engine_secure.c \
engine_user.c \ engine_user.c \
gammasection.c \
randomizer.c, randomizer.c,
no,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1) no,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
PHP_INSTALL_HEADERS([ext/random], [php_random.h]) PHP_INSTALL_HEADERS([ext/random], [php_random.h])

View file

@ -1,4 +1,4 @@
EXTENSION("random", "random.c", false /* never shared */, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); EXTENSION("random", "random.c", false /* never shared */, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
PHP_RANDOM="yes"; PHP_RANDOM="yes";
ADD_SOURCES(configure_module_dirname, "engine_combinedlcg.c engine_mt19937.c engine_pcgoneseq128xslrr64.c engine_xoshiro256starstar.c engine_secure.c engine_user.c randomizer.c", "random"); ADD_SOURCES(configure_module_dirname, "engine_combinedlcg.c engine_mt19937.c engine_pcgoneseq128xslrr64.c engine_xoshiro256starstar.c engine_secure.c engine_user.c gammasection.c randomizer.c", "random");
PHP_INSTALL_HEADERS("ext/random", "php_random.h"); PHP_INSTALL_HEADERS("ext/random", "php_random.h");

115
ext/random/gammasection.c Normal file
View file

@ -0,0 +1,115 @@
/*
+----------------------------------------------------------------------+
| Copyright (c) The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| https://www.php.net/license/3_01.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: Tim Düsterhus <timwolla@php.net> |
| |
| Based on code from: Frédéric Goualard |
+----------------------------------------------------------------------+
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "php.h"
#include "php_random.h"
#include <math.h>
/* This file implements the γ-section algorithm as published in:
*
* Drawing Random Floating-Point Numbers from an Interval. Frédéric
* Goualard, ACM Trans. Model. Comput. Simul., 32:3, 2022.
* https://doi.org/10.1145/3503512
*/
static double gamma_low(double x)
{
return x - nextafter(x, -DBL_MAX);
}
static double gamma_high(double x)
{
return nextafter(x, DBL_MAX) - x;
}
static double gamma_max(double x, double y)
{
return (fabs(x) > fabs(y)) ? gamma_high(x) : gamma_low(y);
}
static uint64_t ceilint(double a, double b, double g)
{
double s = b / g - a / g;
double e;
if (fabs(a) <= fabs(b)) {
e = -a / g - (s - b / g);
} else {
e = b / g - (s + a / g);
}
double si = ceil(s);
return (s != si) ? (uint64_t)si : (uint64_t)si + (e > 0);
}
PHPAPI double php_random_gammasection_closed_open(const php_random_algo *algo, php_random_status *status, double min, double max)
{
double g = gamma_max(min, max);
uint64_t hi = ceilint(min, max, g);
uint64_t k = 1 + php_random_range64(algo, status, hi - 1); /* [1, hi] */
if (fabs(min) <= fabs(max)) {
return k == hi ? min : max - k * g;
} else {
return min + (k - 1) * g;
}
}
PHPAPI double php_random_gammasection_closed_closed(const php_random_algo *algo, php_random_status *status, double min, double max)
{
double g = gamma_max(min, max);
uint64_t hi = ceilint(min, max, g);
uint64_t k = php_random_range64(algo, status, hi); /* [0, hi] */
if (fabs(min) <= fabs(max)) {
return k == hi ? min : max - k * g;
} else {
return k == hi ? max : min + k * g;
}
}
PHPAPI double php_random_gammasection_open_closed(const php_random_algo *algo, php_random_status *status, double min, double max)
{
double g = gamma_max(min, max);
uint64_t hi = ceilint(min, max, g);
uint64_t k = php_random_range64(algo, status, hi - 1); /* [0, hi - 1] */
if (fabs(min) <= fabs(max)) {
return max - k * g;
} else {
return k == (hi - 1) ? max : min + (k + 1) * g;
}
}
PHPAPI double php_random_gammasection_open_open(const php_random_algo *algo, php_random_status *status, double min, double max)
{
double g = gamma_max(min, max);
uint64_t hi = ceilint(min, max, g);
uint64_t k = 1 + php_random_range64(algo, status, hi - 2); /* [1, hi - 1] */
if (fabs(min) <= fabs(max)) {
return max - k * g;
} else {
return min + k * g;
}
}

View file

@ -270,8 +270,11 @@ extern PHPAPI zend_class_entry *random_ce_Random_Engine_PcgOneseq128XslRr64;
extern PHPAPI zend_class_entry *random_ce_Random_Engine_Mt19937; extern PHPAPI zend_class_entry *random_ce_Random_Engine_Mt19937;
extern PHPAPI zend_class_entry *random_ce_Random_Engine_Xoshiro256StarStar; extern PHPAPI zend_class_entry *random_ce_Random_Engine_Xoshiro256StarStar;
extern PHPAPI zend_class_entry *random_ce_Random_Engine_Secure; extern PHPAPI zend_class_entry *random_ce_Random_Engine_Secure;
extern PHPAPI zend_class_entry *random_ce_Random_Randomizer; extern PHPAPI zend_class_entry *random_ce_Random_Randomizer;
extern PHPAPI zend_class_entry *random_ce_Random_IntervalBoundary;
static inline php_random_engine *php_random_engine_from_obj(zend_object *object) { static inline php_random_engine *php_random_engine_from_obj(zend_object *object) {
return (php_random_engine *)((char *)(object) - XtOffsetOf(php_random_engine, std)); return (php_random_engine *)((char *)(object) - XtOffsetOf(php_random_engine, std));
} }
@ -290,6 +293,8 @@ PHPAPI void php_random_status_free(php_random_status *status, const bool persist
PHPAPI php_random_engine *php_random_engine_common_init(zend_class_entry *ce, zend_object_handlers *handlers, const php_random_algo *algo); PHPAPI php_random_engine *php_random_engine_common_init(zend_class_entry *ce, zend_object_handlers *handlers, const php_random_algo *algo);
PHPAPI void php_random_engine_common_free_object(zend_object *object); PHPAPI void php_random_engine_common_free_object(zend_object *object);
PHPAPI zend_object *php_random_engine_common_clone_object(zend_object *object); PHPAPI zend_object *php_random_engine_common_clone_object(zend_object *object);
PHPAPI uint32_t php_random_range32(const php_random_algo *algo, php_random_status *status, uint32_t umax);
PHPAPI uint64_t php_random_range64(const php_random_algo *algo, php_random_status *status, uint64_t umax);
PHPAPI zend_long php_random_range(const php_random_algo *algo, php_random_status *status, zend_long min, zend_long max); PHPAPI zend_long php_random_range(const php_random_algo *algo, php_random_status *status, zend_long min, zend_long max);
PHPAPI const php_random_algo *php_random_default_algo(void); PHPAPI const php_random_algo *php_random_default_algo(void);
PHPAPI php_random_status *php_random_default_status(void); PHPAPI php_random_status *php_random_default_status(void);
@ -306,6 +311,11 @@ PHPAPI void php_random_pcgoneseq128xslrr64_advance(php_random_status_state_pcgon
PHPAPI void php_random_xoshiro256starstar_jump(php_random_status_state_xoshiro256starstar *state); PHPAPI void php_random_xoshiro256starstar_jump(php_random_status_state_xoshiro256starstar *state);
PHPAPI void php_random_xoshiro256starstar_jump_long(php_random_status_state_xoshiro256starstar *state); PHPAPI void php_random_xoshiro256starstar_jump_long(php_random_status_state_xoshiro256starstar *state);
PHPAPI double php_random_gammasection_closed_open(const php_random_algo *algo, php_random_status *status, double min, double max);
PHPAPI double php_random_gammasection_closed_closed(const php_random_algo *algo, php_random_status *status, double min, double max);
PHPAPI double php_random_gammasection_open_closed(const php_random_algo *algo, php_random_status *status, double min, double max);
PHPAPI double php_random_gammasection_open_open(const php_random_algo *algo, php_random_status *status, double min, double max);
extern zend_module_entry random_module_entry; extern zend_module_entry random_module_entry;
# define phpext_random_ptr &random_module_entry # define phpext_random_ptr &random_module_entry

View file

@ -26,6 +26,7 @@
#include "php.h" #include "php.h"
#include "Zend/zend_enum.h"
#include "Zend/zend_exceptions.h" #include "Zend/zend_exceptions.h"
#include "php_random.h" #include "php_random.h"
@ -76,6 +77,8 @@ PHPAPI zend_class_entry *random_ce_Random_Engine_Secure;
PHPAPI zend_class_entry *random_ce_Random_Randomizer; PHPAPI zend_class_entry *random_ce_Random_Randomizer;
PHPAPI zend_class_entry *random_ce_Random_IntervalBoundary;
PHPAPI zend_class_entry *random_ce_Random_RandomError; PHPAPI zend_class_entry *random_ce_Random_RandomError;
PHPAPI zend_class_entry *random_ce_Random_BrokenRandomEngineError; PHPAPI zend_class_entry *random_ce_Random_BrokenRandomEngineError;
PHPAPI zend_class_entry *random_ce_Random_RandomException; PHPAPI zend_class_entry *random_ce_Random_RandomException;
@ -86,7 +89,7 @@ static zend_object_handlers random_engine_xoshiro256starstar_object_handlers;
static zend_object_handlers random_engine_secure_object_handlers; static zend_object_handlers random_engine_secure_object_handlers;
static zend_object_handlers random_randomizer_object_handlers; static zend_object_handlers random_randomizer_object_handlers;
static inline uint32_t rand_range32(const php_random_algo *algo, php_random_status *status, uint32_t umax) PHPAPI uint32_t php_random_range32(const php_random_algo *algo, php_random_status *status, uint32_t umax)
{ {
uint32_t result, limit; uint32_t result, limit;
size_t total_size = 0; size_t total_size = 0;
@ -142,7 +145,7 @@ static inline uint32_t rand_range32(const php_random_algo *algo, php_random_stat
return result % umax; return result % umax;
} }
static inline uint64_t rand_range64(const php_random_algo *algo, php_random_status *status, uint64_t umax) PHPAPI uint64_t php_random_range64(const php_random_algo *algo, php_random_status *status, uint64_t umax)
{ {
uint64_t result, limit; uint64_t result, limit;
size_t total_size = 0; size_t total_size = 0;
@ -310,10 +313,10 @@ PHPAPI zend_long php_random_range(const php_random_algo *algo, php_random_status
zend_ulong umax = (zend_ulong) max - (zend_ulong) min; zend_ulong umax = (zend_ulong) max - (zend_ulong) min;
if (umax > UINT32_MAX) { if (umax > UINT32_MAX) {
return (zend_long) (rand_range64(algo, status, umax) + min); return (zend_long) (php_random_range64(algo, status, umax) + min);
} }
return (zend_long) (rand_range32(algo, status, umax) + min); return (zend_long) (php_random_range32(algo, status, umax) + min);
} }
/* }}} */ /* }}} */
@ -896,6 +899,9 @@ PHP_MINIT_FUNCTION(random)
random_randomizer_object_handlers.free_obj = randomizer_free_obj; random_randomizer_object_handlers.free_obj = randomizer_free_obj;
random_randomizer_object_handlers.clone_obj = NULL; random_randomizer_object_handlers.clone_obj = NULL;
/* Random\IntervalBoundary */
random_ce_Random_IntervalBoundary = register_class_Random_IntervalBoundary();
register_random_symbols(module_number); register_random_symbols(module_number);
return SUCCESS; return SUCCESS;

View file

@ -133,6 +133,10 @@ namespace Random
public function nextInt(): int {} public function nextInt(): int {}
public function nextFloat(): float {}
public function getFloat(float $min, float $max, IntervalBoundary $boundary = IntervalBoundary::ClosedOpen): float {}
public function getInt(int $min, int $max): int {} public function getInt(int $min, int $max): int {}
public function getBytes(int $length): string {} public function getBytes(int $length): string {}
@ -150,6 +154,13 @@ namespace Random
public function __unserialize(array $data): void {} public function __unserialize(array $data): void {}
} }
enum IntervalBoundary {
case ClosedOpen;
case ClosedClosed;
case OpenClosed;
case OpenOpen;
}
/** /**
* @strict-properties * @strict-properties
*/ */

View file

@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead. /* This is a generated file, edit the .stub.php file instead.
* Stub hash: a4226bc7838eba98c5a935b279f681a7d083c0b2 */ * Stub hash: 7b9594d2eadb778ecec34114b67f2d0ae8bbb58a */
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_lcg_value, 0, 0, IS_DOUBLE, 0) ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_lcg_value, 0, 0, IS_DOUBLE, 0)
ZEND_END_ARG_INFO() ZEND_END_ARG_INFO()
@ -90,6 +90,14 @@ ZEND_END_ARG_INFO()
#define arginfo_class_Random_Randomizer_nextInt arginfo_mt_getrandmax #define arginfo_class_Random_Randomizer_nextInt arginfo_mt_getrandmax
#define arginfo_class_Random_Randomizer_nextFloat arginfo_lcg_value
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Random_Randomizer_getFloat, 0, 2, IS_DOUBLE, 0)
ZEND_ARG_TYPE_INFO(0, min, IS_DOUBLE, 0)
ZEND_ARG_TYPE_INFO(0, max, IS_DOUBLE, 0)
ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, boundary, Random\\IntervalBoundary, 0, "Random\\IntervalBoundary::ClosedOpen")
ZEND_END_ARG_INFO()
#define arginfo_class_Random_Randomizer_getInt arginfo_random_int #define arginfo_class_Random_Randomizer_getInt arginfo_random_int
#define arginfo_class_Random_Randomizer_getBytes arginfo_random_bytes #define arginfo_class_Random_Randomizer_getBytes arginfo_random_bytes
@ -136,6 +144,8 @@ ZEND_METHOD(Random_Engine_Xoshiro256StarStar, jump);
ZEND_METHOD(Random_Engine_Xoshiro256StarStar, jumpLong); ZEND_METHOD(Random_Engine_Xoshiro256StarStar, jumpLong);
ZEND_METHOD(Random_Randomizer, __construct); ZEND_METHOD(Random_Randomizer, __construct);
ZEND_METHOD(Random_Randomizer, nextInt); ZEND_METHOD(Random_Randomizer, nextInt);
ZEND_METHOD(Random_Randomizer, nextFloat);
ZEND_METHOD(Random_Randomizer, getFloat);
ZEND_METHOD(Random_Randomizer, getInt); ZEND_METHOD(Random_Randomizer, getInt);
ZEND_METHOD(Random_Randomizer, getBytes); ZEND_METHOD(Random_Randomizer, getBytes);
ZEND_METHOD(Random_Randomizer, getBytesFromString); ZEND_METHOD(Random_Randomizer, getBytesFromString);
@ -213,6 +223,8 @@ static const zend_function_entry class_Random_CryptoSafeEngine_methods[] = {
static const zend_function_entry class_Random_Randomizer_methods[] = { static const zend_function_entry class_Random_Randomizer_methods[] = {
ZEND_ME(Random_Randomizer, __construct, arginfo_class_Random_Randomizer___construct, ZEND_ACC_PUBLIC) ZEND_ME(Random_Randomizer, __construct, arginfo_class_Random_Randomizer___construct, ZEND_ACC_PUBLIC)
ZEND_ME(Random_Randomizer, nextInt, arginfo_class_Random_Randomizer_nextInt, ZEND_ACC_PUBLIC) ZEND_ME(Random_Randomizer, nextInt, arginfo_class_Random_Randomizer_nextInt, ZEND_ACC_PUBLIC)
ZEND_ME(Random_Randomizer, nextFloat, arginfo_class_Random_Randomizer_nextFloat, ZEND_ACC_PUBLIC)
ZEND_ME(Random_Randomizer, getFloat, arginfo_class_Random_Randomizer_getFloat, ZEND_ACC_PUBLIC)
ZEND_ME(Random_Randomizer, getInt, arginfo_class_Random_Randomizer_getInt, ZEND_ACC_PUBLIC) ZEND_ME(Random_Randomizer, getInt, arginfo_class_Random_Randomizer_getInt, ZEND_ACC_PUBLIC)
ZEND_ME(Random_Randomizer, getBytes, arginfo_class_Random_Randomizer_getBytes, ZEND_ACC_PUBLIC) ZEND_ME(Random_Randomizer, getBytes, arginfo_class_Random_Randomizer_getBytes, ZEND_ACC_PUBLIC)
ZEND_ME(Random_Randomizer, getBytesFromString, arginfo_class_Random_Randomizer_getBytesFromString, ZEND_ACC_PUBLIC) ZEND_ME(Random_Randomizer, getBytesFromString, arginfo_class_Random_Randomizer_getBytesFromString, ZEND_ACC_PUBLIC)
@ -225,6 +237,11 @@ static const zend_function_entry class_Random_Randomizer_methods[] = {
}; };
static const zend_function_entry class_Random_IntervalBoundary_methods[] = {
ZEND_FE_END
};
static const zend_function_entry class_Random_RandomError_methods[] = { static const zend_function_entry class_Random_RandomError_methods[] = {
ZEND_FE_END ZEND_FE_END
}; };
@ -332,6 +349,21 @@ static zend_class_entry *register_class_Random_Randomizer(void)
return class_entry; return class_entry;
} }
static zend_class_entry *register_class_Random_IntervalBoundary(void)
{
zend_class_entry *class_entry = zend_register_internal_enum("Random\\IntervalBoundary", IS_UNDEF, class_Random_IntervalBoundary_methods);
zend_enum_add_case_cstr(class_entry, "ClosedOpen", NULL);
zend_enum_add_case_cstr(class_entry, "ClosedClosed", NULL);
zend_enum_add_case_cstr(class_entry, "OpenClosed", NULL);
zend_enum_add_case_cstr(class_entry, "OpenOpen", NULL);
return class_entry;
}
static zend_class_entry *register_class_Random_RandomError(zend_class_entry *class_entry_Error) static zend_class_entry *register_class_Random_RandomError(zend_class_entry *class_entry_Error)
{ {
zend_class_entry ce, *class_entry; zend_class_entry ce, *class_entry;

View file

@ -24,6 +24,7 @@
#include "ext/standard/php_array.h" #include "ext/standard/php_array.h"
#include "ext/standard/php_string.h" #include "ext/standard/php_string.h"
#include "Zend/zend_enum.h"
#include "Zend/zend_exceptions.h" #include "Zend/zend_exceptions.h"
static inline void randomizer_common_init(php_random_randomizer *randomizer, zend_object *engine_object) { static inline void randomizer_common_init(php_random_randomizer *randomizer, zend_object *engine_object) {
@ -88,6 +89,114 @@ PHP_METHOD(Random_Randomizer, __construct)
} }
/* }}} */ /* }}} */
/* {{{ Generate a float in [0, 1) */
PHP_METHOD(Random_Randomizer, nextFloat)
{
php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
uint64_t result;
size_t total_size;
ZEND_PARSE_PARAMETERS_NONE();
result = 0;
total_size = 0;
do {
uint64_t r = randomizer->algo->generate(randomizer->status);
result = result | (r << (total_size * 8));
total_size += randomizer->status->last_generated_size;
if (EG(exception)) {
RETURN_THROWS();
}
} while (total_size < sizeof(uint64_t));
/* A double has 53 bits of precision, thus we must not
* use the full 64 bits of the uint64_t, because we would
* introduce a bias / rounding error.
*/
#if DBL_MANT_DIG != 53
# error "Random_Randomizer::nextFloat(): Requires DBL_MANT_DIG == 53 to work."
#endif
const double step_size = 1.0 / (1ULL << 53);
/* Use the upper 53 bits, because some engine's lower bits
* are of lower quality.
*/
result = (result >> 11);
RETURN_DOUBLE(step_size * result);
}
/* }}} */
/* {{{ Generates a random float within a configurable interval.
*
* This method uses the γ-section algorithm by Frédéric Goualard.
*/
PHP_METHOD(Random_Randomizer, getFloat)
{
php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
double min, max;
zend_object *bounds = NULL;
int bounds_type = 'C' + sizeof("ClosedOpen") - 1;
ZEND_PARSE_PARAMETERS_START(2, 3)
Z_PARAM_DOUBLE(min)
Z_PARAM_DOUBLE(max)
Z_PARAM_OPTIONAL
Z_PARAM_OBJ_OF_CLASS(bounds, random_ce_Random_IntervalBoundary);
ZEND_PARSE_PARAMETERS_END();
if (!zend_finite(min)) {
zend_argument_value_error(1, "must be finite");
RETURN_THROWS();
}
if (!zend_finite(max)) {
zend_argument_value_error(2, "must be finite");
RETURN_THROWS();
}
if (bounds) {
zval *case_name = zend_enum_fetch_case_name(bounds);
zend_string *bounds_name = Z_STR_P(case_name);
bounds_type = ZSTR_VAL(bounds_name)[0] + ZSTR_LEN(bounds_name);
}
switch (bounds_type) {
case 'C' + sizeof("ClosedOpen") - 1:
if (UNEXPECTED(max <= min)) {
zend_argument_value_error(2, "must be greater than argument #1 ($min)");
RETURN_THROWS();
}
RETURN_DOUBLE(php_random_gammasection_closed_open(randomizer->algo, randomizer->status, min, max));
case 'C' + sizeof("ClosedClosed") - 1:
if (UNEXPECTED(max < min)) {
zend_argument_value_error(2, "must be greater than or equal to argument #1 ($min)");
RETURN_THROWS();
}
RETURN_DOUBLE(php_random_gammasection_closed_closed(randomizer->algo, randomizer->status, min, max));
case 'O' + sizeof("OpenClosed") - 1:
if (UNEXPECTED(max <= min)) {
zend_argument_value_error(2, "must be greater than argument #1 ($min)");
RETURN_THROWS();
}
RETURN_DOUBLE(php_random_gammasection_open_closed(randomizer->algo, randomizer->status, min, max));
case 'O' + sizeof("OpenOpen") - 1:
if (UNEXPECTED(max <= min)) {
zend_argument_value_error(2, "must be greater than argument #1 ($min)");
RETURN_THROWS();
}
RETURN_DOUBLE(php_random_gammasection_open_open(randomizer->algo, randomizer->status, min, max));
default:
ZEND_UNREACHABLE();
}
}
/* }}} */
/* {{{ Generate positive random number */ /* {{{ Generate positive random number */
PHP_METHOD(Random_Randomizer, nextInt) PHP_METHOD(Random_Randomizer, nextInt)
{ {

View file

@ -0,0 +1,50 @@
--TEST--
Random: Randomizer: getFloat(): Basic functionality
--FILE--
<?php
use Random\Engine;
use Random\Engine\Mt19937;
use Random\Engine\PcgOneseq128XslRr64;
use Random\Engine\Secure;
use Random\Engine\Test\TestShaEngine;
use Random\Engine\Xoshiro256StarStar;
use Random\IntervalBoundary;
use Random\Randomizer;
require __DIR__ . "/../../engines.inc";
$engines = [];
$engines[] = new Mt19937(null, MT_RAND_MT19937);
$engines[] = new Mt19937(null, MT_RAND_PHP);
$engines[] = new PcgOneseq128XslRr64();
$engines[] = new Xoshiro256StarStar();
$engines[] = new Secure();
$engines[] = new TestShaEngine();
foreach ($engines as $engine) {
echo $engine::class, PHP_EOL;
$randomizer = new Randomizer($engine);
// Basic range test.
for ($i = 0.0; $i < 10_000.0; $i += 1.2345) {
$result = $randomizer->getFloat(-$i, $i, IntervalBoundary::ClosedClosed);
if ($result > $i || $result < -$i) {
die("failure: out of range at {$i}");
}
}
}
die('success');
?>
--EXPECT--
Random\Engine\Mt19937
Random\Engine\Mt19937
Random\Engine\PcgOneseq128XslRr64
Random\Engine\Xoshiro256StarStar
Random\Engine\Secure
Random\Engine\Test\TestShaEngine
success

View file

@ -0,0 +1,119 @@
--TEST--
Random: Randomizer: getFloat(): Parameters are correctly validated
--FILE--
<?php
use Random\IntervalBoundary;
use Random\Randomizer;
function randomizer(): Randomizer
{
return new Randomizer();
}
foreach ([
IntervalBoundary::ClosedClosed,
IntervalBoundary::ClosedOpen,
IntervalBoundary::OpenClosed,
IntervalBoundary::OpenOpen,
] as $boundary) {
echo $boundary->name, PHP_EOL;
try {
var_dump(randomizer()->getFloat(NAN, 0.0, $boundary));
} catch (ValueError $e) {
echo $e->getMessage(), PHP_EOL;
}
try {
var_dump(randomizer()->getFloat(INF, 0.0, $boundary));
} catch (ValueError $e) {
echo $e->getMessage(), PHP_EOL;
}
try {
var_dump(randomizer()->getFloat(-INF, 0.0, $boundary));
} catch (ValueError $e) {
echo $e->getMessage(), PHP_EOL;
}
try {
var_dump(randomizer()->getFloat(0.0, NAN, $boundary));
} catch (ValueError $e) {
echo $e->getMessage(), PHP_EOL;
}
try {
var_dump(randomizer()->getFloat(0.0, INF, $boundary));
} catch (ValueError $e) {
echo $e->getMessage(), PHP_EOL;
}
try {
var_dump(randomizer()->getFloat(0.0, -INF, $boundary));
} catch (ValueError $e) {
echo $e->getMessage(), PHP_EOL;
}
try {
var_dump(randomizer()->getFloat(0.0, -0.1, $boundary));
} catch (ValueError $e) {
echo $e->getMessage(), PHP_EOL;
}
try {
var_dump(randomizer()->getFloat(0.0, 0.0, $boundary));
} catch (ValueError $e) {
echo $e->getMessage(), PHP_EOL;
}
try {
// Both values round to the same float.
var_dump(randomizer()->getFloat(100_000_000_000_000_000.0, 100_000_000_000_000_000.1, $boundary));
} catch (ValueError $e) {
echo $e->getMessage(), PHP_EOL;
}
}
?>
--EXPECT--
ClosedClosed
Random\Randomizer::getFloat(): Argument #1 ($min) must be finite
Random\Randomizer::getFloat(): Argument #1 ($min) must be finite
Random\Randomizer::getFloat(): Argument #1 ($min) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be greater than or equal to argument #1 ($min)
float(0)
float(1.0E+17)
ClosedOpen
Random\Randomizer::getFloat(): Argument #1 ($min) must be finite
Random\Randomizer::getFloat(): Argument #1 ($min) must be finite
Random\Randomizer::getFloat(): Argument #1 ($min) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be greater than argument #1 ($min)
Random\Randomizer::getFloat(): Argument #2 ($max) must be greater than argument #1 ($min)
Random\Randomizer::getFloat(): Argument #2 ($max) must be greater than argument #1 ($min)
OpenClosed
Random\Randomizer::getFloat(): Argument #1 ($min) must be finite
Random\Randomizer::getFloat(): Argument #1 ($min) must be finite
Random\Randomizer::getFloat(): Argument #1 ($min) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be greater than argument #1 ($min)
Random\Randomizer::getFloat(): Argument #2 ($max) must be greater than argument #1 ($min)
Random\Randomizer::getFloat(): Argument #2 ($max) must be greater than argument #1 ($min)
OpenOpen
Random\Randomizer::getFloat(): Argument #1 ($min) must be finite
Random\Randomizer::getFloat(): Argument #1 ($min) must be finite
Random\Randomizer::getFloat(): Argument #1 ($min) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be greater than argument #1 ($min)
Random\Randomizer::getFloat(): Argument #2 ($max) must be greater than argument #1 ($min)
Random\Randomizer::getFloat(): Argument #2 ($max) must be greater than argument #1 ($min)

View file

@ -0,0 +1,49 @@
--TEST--
Random: Randomizer: nextFloat(): Basic functionality
--FILE--
<?php
use Random\Engine;
use Random\Engine\Mt19937;
use Random\Engine\PcgOneseq128XslRr64;
use Random\Engine\Secure;
use Random\Engine\Test\TestShaEngine;
use Random\Engine\Xoshiro256StarStar;
use Random\Randomizer;
require __DIR__ . "/../../engines.inc";
$engines = [];
$engines[] = new Mt19937(null, MT_RAND_MT19937);
$engines[] = new Mt19937(null, MT_RAND_PHP);
$engines[] = new PcgOneseq128XslRr64();
$engines[] = new Xoshiro256StarStar();
$engines[] = new Secure();
$engines[] = new TestShaEngine();
foreach ($engines as $engine) {
echo $engine::class, PHP_EOL;
$randomizer = new Randomizer($engine);
// Basic range test.
for ($i = 0; $i < 10_000; $i++) {
$result = $randomizer->nextFloat();
if ($result >= 1 || $result < 0) {
die("failure: out of range at {$i}");
}
}
}
die('success');
?>
--EXPECT--
Random\Engine\Mt19937
Random\Engine\Mt19937
Random\Engine\PcgOneseq128XslRr64
Random\Engine\Xoshiro256StarStar
Random\Engine\Secure
Random\Engine\Test\TestShaEngine
success