Skip to content

Commit f9a1a90

Browse files
authored
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
1 parent 284f61e commit f9a1a90

13 files changed

+511
-6
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ PHP NEWS
6060

6161
- Random:
6262
. Added Randomizer::getBytesFromString(). (Joshua Rüsweg)
63+
. Added Randomizer::nextFloat(), ::getFloat(), and IntervalBoundary. (timwolla)
6364

6465
- Reflection:
6566
. Fix GH-9470 (ReflectionMethod constructor should not find private parent

UPGRADING

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ PHP 8.3 UPGRADE NOTES
6666
- Random:
6767
. Added Randomizer::getBytesFromString().
6868
RFC: https://wiki.php.net/rfc/randomizer_additions
69+
. Added Randomizer::nextFloat(), ::getFloat(), and IntervalBoundary.
70+
RFC: https://wiki.php.net/rfc/randomizer_additions
6971

7072
- Sockets:
7173
. Added socket_atmark to checks if the socket is OOB marked.

ext/random/config.m4

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ PHP_NEW_EXTENSION(random,
2525
engine_xoshiro256starstar.c \
2626
engine_secure.c \
2727
engine_user.c \
28+
gammasection.c \
2829
randomizer.c,
2930
no,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
3031
PHP_INSTALL_HEADERS([ext/random], [php_random.h])

ext/random/config.w32

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
EXTENSION("random", "random.c", false /* never shared */, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
22
PHP_RANDOM="yes";
3-
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");
3+
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");
44
PHP_INSTALL_HEADERS("ext/random", "php_random.h");

ext/random/gammasection.c

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
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+
| https://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: Tim Düsterhus <timwolla@php.net> |
14+
| |
15+
| Based on code from: Frédéric Goualard |
16+
+----------------------------------------------------------------------+
17+
*/
18+
19+
#ifdef HAVE_CONFIG_H
20+
# include "config.h"
21+
#endif
22+
23+
#include "php.h"
24+
#include "php_random.h"
25+
#include <math.h>
26+
27+
/* This file implements the γ-section algorithm as published in:
28+
*
29+
* Drawing Random Floating-Point Numbers from an Interval. Frédéric
30+
* Goualard, ACM Trans. Model. Comput. Simul., 32:3, 2022.
31+
* https://doi.org/10.1145/3503512
32+
*/
33+
34+
static double gamma_low(double x)
35+
{
36+
return x - nextafter(x, -DBL_MAX);
37+
}
38+
39+
static double gamma_high(double x)
40+
{
41+
return nextafter(x, DBL_MAX) - x;
42+
}
43+
44+
static double gamma_max(double x, double y)
45+
{
46+
return (fabs(x) > fabs(y)) ? gamma_high(x) : gamma_low(y);
47+
}
48+
49+
static uint64_t ceilint(double a, double b, double g)
50+
{
51+
double s = b / g - a / g;
52+
double e;
53+
54+
if (fabs(a) <= fabs(b)) {
55+
e = -a / g - (s - b / g);
56+
} else {
57+
e = b / g - (s + a / g);
58+
}
59+
60+
double si = ceil(s);
61+
62+
return (s != si) ? (uint64_t)si : (uint64_t)si + (e > 0);
63+
}
64+
65+
PHPAPI double php_random_gammasection_closed_open(const php_random_algo *algo, php_random_status *status, double min, double max)
66+
{
67+
double g = gamma_max(min, max);
68+
uint64_t hi = ceilint(min, max, g);
69+
uint64_t k = 1 + php_random_range64(algo, status, hi - 1); /* [1, hi] */
70+
71+
if (fabs(min) <= fabs(max)) {
72+
return k == hi ? min : max - k * g;
73+
} else {
74+
return min + (k - 1) * g;
75+
}
76+
}
77+
78+
PHPAPI double php_random_gammasection_closed_closed(const php_random_algo *algo, php_random_status *status, double min, double max)
79+
{
80+
double g = gamma_max(min, max);
81+
uint64_t hi = ceilint(min, max, g);
82+
uint64_t k = php_random_range64(algo, status, hi); /* [0, hi] */
83+
84+
if (fabs(min) <= fabs(max)) {
85+
return k == hi ? min : max - k * g;
86+
} else {
87+
return k == hi ? max : min + k * g;
88+
}
89+
}
90+
91+
PHPAPI double php_random_gammasection_open_closed(const php_random_algo *algo, php_random_status *status, double min, double max)
92+
{
93+
double g = gamma_max(min, max);
94+
uint64_t hi = ceilint(min, max, g);
95+
uint64_t k = php_random_range64(algo, status, hi - 1); /* [0, hi - 1] */
96+
97+
if (fabs(min) <= fabs(max)) {
98+
return max - k * g;
99+
} else {
100+
return k == (hi - 1) ? max : min + (k + 1) * g;
101+
}
102+
}
103+
104+
PHPAPI double php_random_gammasection_open_open(const php_random_algo *algo, php_random_status *status, double min, double max)
105+
{
106+
double g = gamma_max(min, max);
107+
uint64_t hi = ceilint(min, max, g);
108+
uint64_t k = 1 + php_random_range64(algo, status, hi - 2); /* [1, hi - 1] */
109+
110+
if (fabs(min) <= fabs(max)) {
111+
return max - k * g;
112+
} else {
113+
return min + k * g;
114+
}
115+
}

ext/random/php_random.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,11 @@ extern PHPAPI zend_class_entry *random_ce_Random_Engine_PcgOneseq128XslRr64;
270270
extern PHPAPI zend_class_entry *random_ce_Random_Engine_Mt19937;
271271
extern PHPAPI zend_class_entry *random_ce_Random_Engine_Xoshiro256StarStar;
272272
extern PHPAPI zend_class_entry *random_ce_Random_Engine_Secure;
273+
273274
extern PHPAPI zend_class_entry *random_ce_Random_Randomizer;
274275

276+
extern PHPAPI zend_class_entry *random_ce_Random_IntervalBoundary;
277+
275278
static inline php_random_engine *php_random_engine_from_obj(zend_object *object) {
276279
return (php_random_engine *)((char *)(object) - XtOffsetOf(php_random_engine, std));
277280
}
@@ -290,6 +293,8 @@ PHPAPI void php_random_status_free(php_random_status *status, const bool persist
290293
PHPAPI php_random_engine *php_random_engine_common_init(zend_class_entry *ce, zend_object_handlers *handlers, const php_random_algo *algo);
291294
PHPAPI void php_random_engine_common_free_object(zend_object *object);
292295
PHPAPI zend_object *php_random_engine_common_clone_object(zend_object *object);
296+
PHPAPI uint32_t php_random_range32(const php_random_algo *algo, php_random_status *status, uint32_t umax);
297+
PHPAPI uint64_t php_random_range64(const php_random_algo *algo, php_random_status *status, uint64_t umax);
293298
PHPAPI zend_long php_random_range(const php_random_algo *algo, php_random_status *status, zend_long min, zend_long max);
294299
PHPAPI const php_random_algo *php_random_default_algo(void);
295300
PHPAPI php_random_status *php_random_default_status(void);
@@ -306,6 +311,11 @@ PHPAPI void php_random_pcgoneseq128xslrr64_advance(php_random_status_state_pcgon
306311
PHPAPI void php_random_xoshiro256starstar_jump(php_random_status_state_xoshiro256starstar *state);
307312
PHPAPI void php_random_xoshiro256starstar_jump_long(php_random_status_state_xoshiro256starstar *state);
308313

314+
PHPAPI double php_random_gammasection_closed_open(const php_random_algo *algo, php_random_status *status, double min, double max);
315+
PHPAPI double php_random_gammasection_closed_closed(const php_random_algo *algo, php_random_status *status, double min, double max);
316+
PHPAPI double php_random_gammasection_open_closed(const php_random_algo *algo, php_random_status *status, double min, double max);
317+
PHPAPI double php_random_gammasection_open_open(const php_random_algo *algo, php_random_status *status, double min, double max);
318+
309319
extern zend_module_entry random_module_entry;
310320
# define phpext_random_ptr &random_module_entry
311321

ext/random/random.c

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
#include "php.h"
2828

29+
#include "Zend/zend_enum.h"
2930
#include "Zend/zend_exceptions.h"
3031

3132
#include "php_random.h"
@@ -76,6 +77,8 @@ PHPAPI zend_class_entry *random_ce_Random_Engine_Secure;
7677

7778
PHPAPI zend_class_entry *random_ce_Random_Randomizer;
7879

80+
PHPAPI zend_class_entry *random_ce_Random_IntervalBoundary;
81+
7982
PHPAPI zend_class_entry *random_ce_Random_RandomError;
8083
PHPAPI zend_class_entry *random_ce_Random_BrokenRandomEngineError;
8184
PHPAPI zend_class_entry *random_ce_Random_RandomException;
@@ -86,7 +89,7 @@ static zend_object_handlers random_engine_xoshiro256starstar_object_handlers;
8689
static zend_object_handlers random_engine_secure_object_handlers;
8790
static zend_object_handlers random_randomizer_object_handlers;
8891

89-
static inline uint32_t rand_range32(const php_random_algo *algo, php_random_status *status, uint32_t umax)
92+
PHPAPI uint32_t php_random_range32(const php_random_algo *algo, php_random_status *status, uint32_t umax)
9093
{
9194
uint32_t result, limit;
9295
size_t total_size = 0;
@@ -142,7 +145,7 @@ static inline uint32_t rand_range32(const php_random_algo *algo, php_random_stat
142145
return result % umax;
143146
}
144147

145-
static inline uint64_t rand_range64(const php_random_algo *algo, php_random_status *status, uint64_t umax)
148+
PHPAPI uint64_t php_random_range64(const php_random_algo *algo, php_random_status *status, uint64_t umax)
146149
{
147150
uint64_t result, limit;
148151
size_t total_size = 0;
@@ -310,10 +313,10 @@ PHPAPI zend_long php_random_range(const php_random_algo *algo, php_random_status
310313
zend_ulong umax = (zend_ulong) max - (zend_ulong) min;
311314

312315
if (umax > UINT32_MAX) {
313-
return (zend_long) (rand_range64(algo, status, umax) + min);
316+
return (zend_long) (php_random_range64(algo, status, umax) + min);
314317
}
315318

316-
return (zend_long) (rand_range32(algo, status, umax) + min);
319+
return (zend_long) (php_random_range32(algo, status, umax) + min);
317320
}
318321
/* }}} */
319322

@@ -896,6 +899,9 @@ PHP_MINIT_FUNCTION(random)
896899
random_randomizer_object_handlers.free_obj = randomizer_free_obj;
897900
random_randomizer_object_handlers.clone_obj = NULL;
898901

902+
/* Random\IntervalBoundary */
903+
random_ce_Random_IntervalBoundary = register_class_Random_IntervalBoundary();
904+
899905
register_random_symbols(module_number);
900906

901907
return SUCCESS;

ext/random/random.stub.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ public function __construct(?Engine $engine = null) {}
133133

134134
public function nextInt(): int {}
135135

136+
public function nextFloat(): float {}
137+
138+
public function getFloat(float $min, float $max, IntervalBoundary $boundary = IntervalBoundary::ClosedOpen): float {}
139+
136140
public function getInt(int $min, int $max): int {}
137141

138142
public function getBytes(int $length): string {}
@@ -150,6 +154,13 @@ public function __serialize(): array {}
150154
public function __unserialize(array $data): void {}
151155
}
152156

157+
enum IntervalBoundary {
158+
case ClosedOpen;
159+
case ClosedClosed;
160+
case OpenClosed;
161+
case OpenOpen;
162+
}
163+
153164
/**
154165
* @strict-properties
155166
*/

ext/random/random_arginfo.h

Lines changed: 33 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)