diff --git a/builtin-functions/kphp-light/stdlib/math-functions.txt b/builtin-functions/kphp-light/stdlib/math-functions.txt index 1064339637..0067117bc7 100644 --- a/builtin-functions/kphp-light/stdlib/math-functions.txt +++ b/builtin-functions/kphp-light/stdlib/math-functions.txt @@ -166,6 +166,8 @@ function lcg_value() ::: float; function random_bytes($length ::: int) ::: string | false; +function random_int($l ::: int, $r ::: int) ::: int | false; + /** @kphp-extern-func-info interruptible */ function uniqid ($prefix ::: string = '', $more_entropy ::: bool = false) ::: string; @@ -185,5 +187,3 @@ define('PHP_ROUND_HALF_DOWN', 123423144); define('PHP_ROUND_HALF_EVEN', 123423145); define('PHP_ROUND_HALF_ODD', 123423146); -/** @kphp-extern-func-info stub generation-required */ -function random_int($l ::: int, $r ::: int) ::: int | false; diff --git a/runtime-light/stdlib/math/random-functions.h b/runtime-light/stdlib/math/random-functions.h index 50e95680c2..db838af587 100644 --- a/runtime-light/stdlib/math/random-functions.h +++ b/runtime-light/stdlib/math/random-functions.h @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -25,6 +26,7 @@ #include "runtime-common/stdlib/math/random-functions.h" #include "runtime-light/coroutine/task.h" #include "runtime-light/k2-platform/k2-api.h" +#include "runtime-light/stdlib/diagnostics/logs.h" #include "runtime-light/stdlib/math/random-state.h" #include "runtime-light/stdlib/system/system-functions.h" @@ -157,6 +159,48 @@ inline Optional f$random_bytes(int64_t length) noexcept { return str; } +inline Optional f$random_int(int64_t min, int64_t max) noexcept { + if (min > max) [[unlikely]] { + kphp::log::warning("argument #1 ($min) must be less than or equal to argument #2 ($max)"); + return false; + } + + if (min == max) { + return min; + } + + auto umax{static_cast(max) - static_cast(min)}; + + uint64_t trial{}; + if (random_impl_::secure_rand_buf(std::addressof(trial), sizeof(trial)) == -1) [[unlikely]] { + kphp::log::warning("source of randomness cannot be found"); + return false; + } + + // special case where no modulus is required + if (umax == std::numeric_limits::max()) { + return static_cast(trial); + } + + ++umax; // increment the max so the range is inclusive of max + + // powers of two are not biased + if (!std::has_single_bit(umax)) { + // ceiling under which UINT64_MAX % max == 0 + const auto limit{std::numeric_limits::max() - (std::numeric_limits::max() % umax) - 1}; + + // discard numbers over the limit to avoid modulo bias + while (trial > limit) { + if (random_impl_::secure_rand_buf(std::addressof(trial), sizeof(trial)) == -1) [[unlikely]] { + kphp::log::warning("source of randomness cannot be found"); + return false; + } + } + } + + return min + static_cast(trial % umax); +} + inline kphp::coro::task f$uniqid(string prefix = string{}, bool more_entropy = false) noexcept { if (!more_entropy) { co_await f$usleep(1); diff --git a/tests/phpt/dl/386_random_int.php b/tests/phpt/dl/386_random_int.php index 0a038db88a..cbbeb2e3a7 100644 --- a/tests/phpt/dl/386_random_int.php +++ b/tests/phpt/dl/386_random_int.php @@ -2,6 +2,15 @@ = -1 && $x <= 0); + + $x = random_int(-1, 1); + var_dump($x >= -1 && $x <= 1); + + $x = random_int(0, 1); + var_dump($x >= 0 && $x <= 1); + $x = random_int(100, 500); var_dump($x >= 100 && $x <= 500);