From 54f722d27dcaa504880af8048251fbbd9fb7ced7 Mon Sep 17 00:00:00 2001 From: Nicholas Dudfield Date: Wed, 23 Sep 2026 12:37:05 +0700 Subject: [PATCH] fix(basics): map rand_int without the standard distribution std::uniform_int_distribution is not the same on libc++ and libstdc++, so a seeded engine produced different harness event streams on Linux. Rejection-sample a closed range from the engine's own 64-bit output. Seeding overloads are unchanged. --- include/xrpl/basics/random.h | 48 +++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/include/xrpl/basics/random.h b/include/xrpl/basics/random.h index ca65b581d2..12ae16c0d1 100644 --- a/include/xrpl/basics/random.h +++ b/include/xrpl/basics/random.h @@ -49,6 +49,32 @@ namespace detail { // Determines if a type can be called like an Engine template using is_engine = std::is_invocable_r; + +// 64 bits from the engine, independent of how the standard library maps +// engine output onto an integer range. +template +std::uint64_t +randomU64(Engine& engine) +{ + using Result = typename Engine::result_type; + constexpr int digits = std::numeric_limits::digits; + auto const draw = [&engine]() -> std::uint64_t { + return static_cast(engine() - Engine::min()); + }; + if constexpr (digits >= 64) + return draw(); + + std::uint64_t value = 0; + int filled = 0; + while (filled < 64) + { + auto const take = digits < (64 - filled) ? digits : (64 - filled); + auto const mask = (std::uint64_t{1} << take) - 1; + value |= (draw() & mask) << filled; + filled += take; + } + return value; +} } // namespace detail /** Return the default random engine. @@ -116,10 +142,24 @@ rand_int(Engine& engine, Integral min, Integral max) { XRPL_ASSERT(max > min, "ripple::rand_int : max over min inputs"); - // This should have no state and constructing it should - // be very cheap. If that turns out not to be the case - // it could be hand-optimized. - return std::uniform_int_distribution(min, max)(engine); + // Closed interval. Rejection sampling keeps the result uniform and the + // same on libc++ and libstdc++ for a given engine sequence. + using U = std::make_unsigned_t; + auto const span = static_cast(static_cast(max) - static_cast(min)); + auto const count = static_cast(span) + 1u; + if (count == 0) + return static_cast(detail::randomU64(engine)); + + // Values below this threshold are the leftover that would bias x % count. + auto const slack = static_cast(-count) % count; + std::uint64_t draw; + do + { + draw = detail::randomU64(engine); + } while (draw < slack); + + auto const offset = static_cast(draw % count); + return static_cast(static_cast(static_cast(min) + offset)); } template