Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Dec 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ PHP NEWS

- Random:
. Added Randomizer::getBytesFromString(). (Joshua Rüsweg)
. Added Randomizer::nextFloat(), ::getFloat(), and IntervalBoundary. (timwolla)

- Reflection:
. Fix GH-9470 (ReflectionMethod constructor should not find private parent
Expand Down
2 changes: 2 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ PHP 8.3 UPGRADE NOTES
- Random:
. Added Randomizer::getBytesFromString().
RFC: https://wiki.php.net/rfc/randomizer_additions
. Added Randomizer::nextFloat(), ::getFloat(), and IntervalBoundary.
RFC: https://wiki.php.net/rfc/randomizer_additions

- Sockets:
. Added socket_atmark to checks if the socket is OOB marked.
Expand Down
1 change: 1 addition & 0 deletions ext/random/config.m4
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ PHP_NEW_EXTENSION(random,
engine_xoshiro256starstar.c \
engine_secure.c \
engine_user.c \
gammasection.c \
randomizer.c,
no,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
PHP_INSTALL_HEADERS([ext/random], [php_random.h])
2 changes: 1 addition & 1 deletion ext/random/config.w32
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
EXTENSION("random", "random.c", false /* never shared */, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
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");
115 changes: 115 additions & 0 deletions ext/random/gammasection.c
Original file line number Diff line number Diff line change
@@ -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 |
| [email protected] so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Authors: Tim Düsterhus <[email protected]> |
| |
| 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] */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these need exception handling since php_random_range64 can throw, or does 0 work out acceptably in the math and then the error will still be set in the global state?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer that these APIs didn't throw, to be honest -- I like it when the C APIs can be used basically like regular C code without Zend Engine side effects getting in the way, but I'm not going to block the PR on it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@morrisonlevi

or does 0 work out acceptably in the math and then the error will still be set in the global state?

0 is a valid return value that may be returned by php_random_range64 in the happy path, so the result will be well-defined in the error path (even if not particularly random).

I would prefer that these APIs didn't throw,

Depending on the algo (“the engine”) these functions may call into userland, thus needing to deal with Exceptions is unavoidable:

<?php
final class ThrowingEngine implements Random\Engine {
	public function generate(): string
	{
		throw new \Exception('test');
	}
}
$r = new Random\Randomizer(new ThrowingEngine());

$r->getFloat(1, 2);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify, though: php_random_range64 will not throw, unless either the underlying algo throws, or the underlying algo is so horribly biased that it may not be called random (a number is rejected with < 50% chance, 50 rejections thus have a probability of < 2-50 which is less than 1 in a quadrillion).


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;
}
}
10 changes: 10 additions & 0 deletions ext/random/php_random.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_Xoshiro256StarStar;
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_IntervalBoundary;

static inline php_random_engine *php_random_engine_from_obj(zend_object *object) {
return (php_random_engine *)((char *)(object) - XtOffsetOf(php_random_engine, std));
}
Expand All @@ -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 void php_random_engine_common_free_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 const php_random_algo *php_random_default_algo(void);
PHPAPI php_random_status *php_random_default_status(void);
Expand All @@ -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_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;
# define phpext_random_ptr &random_module_entry

Expand Down
14 changes: 10 additions & 4 deletions ext/random/random.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

#include "php.h"

#include "Zend/zend_enum.h"
#include "Zend/zend_exceptions.h"

#include "php_random.h"
Expand Down Expand Up @@ -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_IntervalBoundary;

PHPAPI zend_class_entry *random_ce_Random_RandomError;
PHPAPI zend_class_entry *random_ce_Random_BrokenRandomEngineError;
PHPAPI zend_class_entry *random_ce_Random_RandomException;
Expand All @@ -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_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;
size_t total_size = 0;
Expand Down Expand Up @@ -142,7 +145,7 @@ static inline uint32_t rand_range32(const php_random_algo *algo, php_random_stat
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;
size_t total_size = 0;
Expand Down Expand Up @@ -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;

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);
}
/* }}} */

Expand Down Expand Up @@ -896,6 +899,9 @@ PHP_MINIT_FUNCTION(random)
random_randomizer_object_handlers.free_obj = randomizer_free_obj;
random_randomizer_object_handlers.clone_obj = NULL;

/* Random\IntervalBoundary */
random_ce_Random_IntervalBoundary = register_class_Random_IntervalBoundary();

register_random_symbols(module_number);

return SUCCESS;
Expand Down
11 changes: 11 additions & 0 deletions ext/random/random.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ public function __construct(?Engine $engine = null) {}

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 getBytes(int $length): string {}
Expand All @@ -150,6 +154,13 @@ public function __serialize(): array {}
public function __unserialize(array $data): void {}
}

enum IntervalBoundary {
case ClosedOpen;
case ClosedClosed;
case OpenClosed;
case OpenOpen;
}

/**
* @strict-properties
*/
Expand Down
34 changes: 33 additions & 1 deletion ext/random/random_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading