Skip to content

Commit 9a9be42

Browse files
committed
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
1 parent 6c610c1 commit 9a9be42

File tree

3 files changed

+105
-22
lines changed

3 files changed

+105
-22
lines changed

ext/random/random.stub.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ public function nextInt(): int {}
135135

136136
public function nextFloat(): float {}
137137

138+
public function getFloat(float $min, float $max): float {}
139+
138140
public function getInt(int $min, int $max): int {}
139141

140142
public function getBytes(int $length): string {}

ext/random/random_arginfo.h

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

ext/random/randomizer.c

Lines changed: 95 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -88,27 +88,6 @@ PHP_METHOD(Random_Randomizer, __construct)
8888
}
8989
/* }}} */
9090

91-
/* {{{ Generate positive random number */
92-
PHP_METHOD(Random_Randomizer, nextInt)
93-
{
94-
php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
95-
uint64_t result;
96-
97-
ZEND_PARSE_PARAMETERS_NONE();
98-
99-
result = randomizer->algo->generate(randomizer->status);
100-
if (EG(exception)) {
101-
RETURN_THROWS();
102-
}
103-
if (randomizer->status->last_generated_size > sizeof(zend_long)) {
104-
zend_throw_exception(random_ce_Random_RandomException, "Generated value exceeds size of int", 0);
105-
RETURN_THROWS();
106-
}
107-
108-
RETURN_LONG((zend_long) (result >> 1));
109-
}
110-
/* }}} */
111-
11291
/* {{{ Generate a float in [0, 1) */
11392
PHP_METHOD(Random_Randomizer, nextFloat)
11493
{
@@ -150,6 +129,101 @@ PHP_METHOD(Random_Randomizer, nextFloat)
150129
}
151130
/* }}} */
152131

132+
static double getFloat_gamma_low(double x)
133+
{
134+
return x - nextdown(x);
135+
}
136+
137+
static double getFloat_gamma_high(double x)
138+
{
139+
return nextup(x) - x;
140+
}
141+
142+
static double getFloat_gamma(double x, double y)
143+
{
144+
double high = getFloat_gamma_high(x);
145+
double low = getFloat_gamma_low(y);
146+
147+
return high > low ? high : low;
148+
}
149+
150+
static uint64_t getFloat_ceilint(double a, double b, double g)
151+
{
152+
double s = b / g - a / g;
153+
double e;
154+
155+
if (fabs(a) <= fabs(b)) {
156+
e = -a / g - (s - b / g);
157+
} else {
158+
e = b / g - (s + a / g);
159+
}
160+
161+
double si = ceil(s);
162+
163+
return (s != si) ? (uint64_t)si : (uint64_t)si + (e > 0);
164+
}
165+
166+
/* {{{ Generates a random float within [min, max).
167+
*
168+
* The algorithm used is the γ-section algorithm as published in:
169+
*
170+
* Drawing Random Floating-Point Numbers from an Interval. Frédéric
171+
* Goualard, ACM Trans. Model. Comput. Simul., 32:3, 2022.
172+
* https://doi.org/10.1145/3503512
173+
*/
174+
PHP_METHOD(Random_Randomizer, getFloat)
175+
{
176+
php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
177+
double min, max;
178+
179+
ZEND_PARSE_PARAMETERS_START(2, 2)
180+
Z_PARAM_DOUBLE(min)
181+
Z_PARAM_DOUBLE(max)
182+
ZEND_PARSE_PARAMETERS_END();
183+
184+
#ifndef __STDC_IEC_559__
185+
zend_throw_exception(random_ce_Random_RandomException, "The getFloat() method requires the underlying 'double' representation to be IEEE-754.", 0);
186+
RETURN_THROWS();
187+
#endif
188+
189+
if (UNEXPECTED(max < min)) {
190+
zend_argument_value_error(2, "must be greater than or equal to argument #1 ($min)");
191+
RETURN_THROWS();
192+
}
193+
194+
double g = getFloat_gamma(min, max);
195+
uint64_t hi = getFloat_ceilint(min, max, g);
196+
uint64_t k = randomizer->algo->range(randomizer->status, 1, hi);
197+
198+
if (fabs(min) <= fabs(max)) {
199+
RETURN_DOUBLE(k == hi ? min : max - k * g);
200+
} else {
201+
RETURN_DOUBLE(min + (k - 1) * g);
202+
}
203+
}
204+
/* }}} */
205+
206+
/* {{{ Generate positive random number */
207+
PHP_METHOD(Random_Randomizer, nextInt)
208+
{
209+
php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
210+
uint64_t result;
211+
212+
ZEND_PARSE_PARAMETERS_NONE();
213+
214+
result = randomizer->algo->generate(randomizer->status);
215+
if (EG(exception)) {
216+
RETURN_THROWS();
217+
}
218+
if (randomizer->status->last_generated_size > sizeof(zend_long)) {
219+
zend_throw_exception(random_ce_Random_RandomException, "Generated value exceeds size of int", 0);
220+
RETURN_THROWS();
221+
}
222+
223+
RETURN_LONG((zend_long) (result >> 1));
224+
}
225+
/* }}} */
226+
153227
/* {{{ Generate random number in range */
154228
PHP_METHOD(Random_Randomizer, getInt)
155229
{

0 commit comments

Comments
 (0)