mirror of
https://github.com/php/php-src.git
synced 2025-08-16 05:58:45 +02:00
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:
parent
284f61ee22
commit
f9a1a90380
13 changed files with 511 additions and 6 deletions
1
NEWS
1
NEWS
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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])
|
||||||
|
|
|
@ -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
115
ext/random/gammasection.c
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
34
ext/random/random_arginfo.h
generated
34
ext/random/random_arginfo.h
generated
|
@ -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;
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
50
ext/random/tests/03_randomizer/methods/getFloat.phpt
Normal file
50
ext/random/tests/03_randomizer/methods/getFloat.phpt
Normal 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
|
119
ext/random/tests/03_randomizer/methods/getFloat_error.phpt
Normal file
119
ext/random/tests/03_randomizer/methods/getFloat_error.phpt
Normal 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)
|
49
ext/random/tests/03_randomizer/methods/nextFloat.phpt
Normal file
49
ext/random/tests/03_randomizer/methods/nextFloat.phpt
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue