mirror of
https://github.com/Xahau/xahaud.git
synced 2026-09-27 23:48:05 +00:00
Merge remote-tracking branch 'origin/multiple-application-deterministic-tests' into dsf-portable-rng
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/beast/xor_shift_engine.h>
|
||||
#include <bit>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
@@ -50,30 +51,85 @@ namespace detail {
|
||||
template <class Engine, class Result = typename Engine::result_type>
|
||||
using is_engine = std::is_invocable_r<Result, Engine>;
|
||||
|
||||
// 64 bits from the engine, independent of how the standard library maps
|
||||
// engine output onto an integer range.
|
||||
// 64 bits from the engine. Width comes from max()-min(), not from the
|
||||
// storage type: a 32-bit engine may use a 64-bit result_type.
|
||||
template <class Engine>
|
||||
std::uint64_t
|
||||
randomU64(Engine& engine)
|
||||
{
|
||||
using Result = typename Engine::result_type;
|
||||
constexpr int digits = std::numeric_limits<Result>::digits;
|
||||
static_assert(std::is_unsigned_v<typename Engine::result_type>);
|
||||
static_assert(
|
||||
std::numeric_limits<typename Engine::result_type>::digits <= 64);
|
||||
static_assert(Engine::min() < Engine::max());
|
||||
auto const draw = [&engine]() -> std::uint64_t {
|
||||
return static_cast<std::uint64_t>(engine() - Engine::min());
|
||||
};
|
||||
if constexpr (digits >= 64)
|
||||
return draw();
|
||||
|
||||
std::uint64_t value = 0;
|
||||
int filled = 0;
|
||||
while (filled < 64)
|
||||
// A wrapped cardinality of 0 means 2^64 values: one full-range draw.
|
||||
constexpr auto span =
|
||||
static_cast<std::uint64_t>(Engine::max() - Engine::min());
|
||||
constexpr std::uint64_t range = span + 1u;
|
||||
constexpr bool full = range == 0;
|
||||
constexpr bool powerOfTwo = full || (range & (range - 1u)) == 0;
|
||||
|
||||
if constexpr (powerOfTwo)
|
||||
{
|
||||
auto const take = digits < (64 - filled) ? digits : (64 - filled);
|
||||
auto const mask = (std::uint64_t{1} << take) - 1;
|
||||
value |= (draw() & mask) << filled;
|
||||
filled += take;
|
||||
constexpr int width = full ? 64 : std::bit_width(range) - 1;
|
||||
if constexpr (width >= 64)
|
||||
return draw();
|
||||
|
||||
std::uint64_t value = 0;
|
||||
int filled = 0;
|
||||
while (filled < 64)
|
||||
{
|
||||
auto const take = width < (64 - filled) ? width : (64 - filled);
|
||||
auto const mask = (std::uint64_t{1} << take) - 1;
|
||||
value |= (draw() & mask) << filled;
|
||||
filled += take;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// [rand.adapt.ibits] for w = 64. R is not a power of two.
|
||||
constexpr int m = std::bit_width(range) - 1;
|
||||
constexpr int nCeil = (64 + m - 1) / m;
|
||||
constexpr int w0Try = 64 / nCeil;
|
||||
constexpr auto y0Try = (std::uint64_t{1} << w0Try) * (range >> w0Try);
|
||||
constexpr bool bump =
|
||||
(range - y0Try) > (y0Try / static_cast<std::uint64_t>(nCeil));
|
||||
constexpr int n = bump ? nCeil + 1 : nCeil;
|
||||
constexpr int w0 = 64 / n;
|
||||
constexpr int n0 = n - (64 % n);
|
||||
constexpr auto y0 = (std::uint64_t{1} << w0) * (range >> w0);
|
||||
constexpr auto y1 =
|
||||
(std::uint64_t{1} << (w0 + 1)) * (range >> (w0 + 1));
|
||||
// R == 3 gives n == 65 and w0 == 0. The first group still
|
||||
// consumes its draw even though it contributes no output bits.
|
||||
static_assert(w0 >= 0 && w0 < 63);
|
||||
|
||||
std::uint64_t word = 0;
|
||||
for (int k = 0; k != n0; ++k)
|
||||
{
|
||||
std::uint64_t u;
|
||||
do
|
||||
{
|
||||
u = draw();
|
||||
} while (u >= y0);
|
||||
word = (word << w0) + (u & ((std::uint64_t{1} << w0) - 1));
|
||||
}
|
||||
for (int k = n0; k != n; ++k)
|
||||
{
|
||||
std::uint64_t u;
|
||||
do
|
||||
{
|
||||
u = draw();
|
||||
} while (u >= y1);
|
||||
constexpr int bits = w0 + 1;
|
||||
word = (word << bits) + (u & ((std::uint64_t{1} << bits) - 1));
|
||||
}
|
||||
return word;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
|
||||
299
src/test/basics/random_test.cpp
Normal file
299
src/test/basics/random_test.cpp
Normal file
@@ -0,0 +1,299 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpl/basics/random.h>
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
#include <xrpl/beast/xor_shift_engine.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <random>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
// 32-bit output stored in a 32-bit result.
|
||||
struct Bits32
|
||||
{
|
||||
using result_type = std::uint32_t;
|
||||
result_type n = 0;
|
||||
static constexpr result_type
|
||||
min()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static constexpr result_type
|
||||
max()
|
||||
{
|
||||
return 0xffffffffu;
|
||||
}
|
||||
result_type
|
||||
operator()()
|
||||
{
|
||||
return n++;
|
||||
}
|
||||
};
|
||||
|
||||
// 32-bit output stored in a 64-bit result. This is the Linux mt19937 shape.
|
||||
struct Bits32In64
|
||||
{
|
||||
using result_type = std::uint64_t;
|
||||
result_type n = 0;
|
||||
static constexpr result_type
|
||||
min()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static constexpr result_type
|
||||
max()
|
||||
{
|
||||
return 0xffffffffu;
|
||||
}
|
||||
result_type
|
||||
operator()()
|
||||
{
|
||||
return n++;
|
||||
}
|
||||
};
|
||||
|
||||
// Nonzero minimum, power-of-two range of 256 values.
|
||||
struct NonzeroMin
|
||||
{
|
||||
using result_type = std::uint32_t;
|
||||
result_type n = 0;
|
||||
static constexpr result_type
|
||||
min()
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
static constexpr result_type
|
||||
max()
|
||||
{
|
||||
return 5 + 255;
|
||||
}
|
||||
result_type
|
||||
operator()()
|
||||
{
|
||||
return static_cast<result_type>(min() + (n++ % 256));
|
||||
}
|
||||
};
|
||||
|
||||
// A valid three-value engine exercises the zero-bit first group in
|
||||
// independent_bits_engine. Cycling values make draw consumption explicit.
|
||||
template <class Result, Result Minimum = 0>
|
||||
struct ThreeValues
|
||||
{
|
||||
using result_type = Result;
|
||||
std::size_t calls = 0;
|
||||
|
||||
static constexpr result_type
|
||||
min()
|
||||
{
|
||||
return Minimum;
|
||||
}
|
||||
|
||||
static constexpr result_type
|
||||
max()
|
||||
{
|
||||
return Minimum + 2;
|
||||
}
|
||||
|
||||
result_type
|
||||
operator()()
|
||||
{
|
||||
return static_cast<result_type>(Minimum + (calls++ % 3));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
class random_test : public beast::unit_test::suite
|
||||
{
|
||||
// 8-bit model of reject-then-modulo. slack = 2^w % count, drop the
|
||||
// low slack words, then modulo. Every accepted result must have the
|
||||
// same number of preimages.
|
||||
void
|
||||
testReducedMapping()
|
||||
{
|
||||
testcase("8-bit reject-then-modulo is uniform");
|
||||
constexpr int universe = 256;
|
||||
for (int count = 1; count <= universe; ++count)
|
||||
{
|
||||
int const slack = universe % count;
|
||||
std::array<int, 256> hits{};
|
||||
int accepted = 0;
|
||||
for (int raw = 0; raw < universe; ++raw)
|
||||
{
|
||||
if (raw < slack)
|
||||
continue;
|
||||
++hits[raw % count];
|
||||
++accepted;
|
||||
}
|
||||
BEAST_EXPECT(accepted % count == 0);
|
||||
auto const each = accepted / count;
|
||||
for (int result = 0; result < count; ++result)
|
||||
BEAST_EXPECT(hits[result] == each);
|
||||
}
|
||||
}
|
||||
|
||||
template <class Engine, class Integral>
|
||||
void
|
||||
expectInRange(Engine& engine, Integral min, Integral max, int samples)
|
||||
{
|
||||
for (int i = 0; i < samples; ++i)
|
||||
{
|
||||
auto const value = rand_int(engine, min, max);
|
||||
BEAST_EXPECT(value >= min);
|
||||
BEAST_EXPECT(value <= max);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
testEngineVectors()
|
||||
{
|
||||
testcase("engine range vectors and draw consumption");
|
||||
|
||||
beast::xor_shift_engine full{1};
|
||||
beast::xor_shift_engine fullTwin{1};
|
||||
BEAST_EXPECT(detail::randomU64(full) == fullTwin());
|
||||
BEAST_EXPECT(full() == fullTwin());
|
||||
|
||||
Bits32 narrow;
|
||||
Bits32 narrowTwin;
|
||||
auto const narrowWord = detail::randomU64(narrow);
|
||||
auto const nLow = static_cast<std::uint64_t>(narrowTwin());
|
||||
auto const nHigh = static_cast<std::uint64_t>(narrowTwin());
|
||||
BEAST_EXPECT(narrowWord == (nLow | (nHigh << 32)));
|
||||
BEAST_EXPECT(narrow() == narrowTwin());
|
||||
|
||||
Bits32In64 wideStore;
|
||||
Bits32In64 wideStoreTwin;
|
||||
auto const wideWord = detail::randomU64(wideStore);
|
||||
auto const wLow = wideStoreTwin();
|
||||
auto const wHigh = wideStoreTwin();
|
||||
BEAST_EXPECT(wideWord == (wLow | (wHigh << 32)));
|
||||
BEAST_EXPECT(wideStore() == wideStoreTwin());
|
||||
|
||||
NonzeroMin shifted;
|
||||
NonzeroMin shiftedTwin;
|
||||
std::uint64_t composed = 0;
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
auto const piece =
|
||||
static_cast<std::uint64_t>(shiftedTwin() - NonzeroMin::min());
|
||||
composed |= piece << (8 * i);
|
||||
}
|
||||
BEAST_EXPECT(detail::randomU64(shifted) == composed);
|
||||
BEAST_EXPECT(shifted() == shiftedTwin());
|
||||
|
||||
std::minstd_rand uneven{12345};
|
||||
std::minstd_rand unevenCopy{12345};
|
||||
std::independent_bits_engine<std::minstd_rand, 64, std::uint64_t> ibits{
|
||||
unevenCopy};
|
||||
for (int i = 0; i < 8; ++i)
|
||||
BEAST_EXPECT(detail::randomU64(uneven) == ibits());
|
||||
}
|
||||
|
||||
void
|
||||
testZeroBitGroup()
|
||||
{
|
||||
testcase("three-value engines retain the zero-bit group draw");
|
||||
auto const check = [&](auto engine) {
|
||||
using Engine = decltype(engine);
|
||||
std::independent_bits_engine<Engine, 64, std::uint64_t> reference{
|
||||
engine};
|
||||
|
||||
// R=3: n=65, w0=0, n0=1, y0=3, y1=2. Discard the
|
||||
// first draw, then take 64 bits, rejecting normalized value 2.
|
||||
// For this cycle the accepted bits are 1010...10 (97 draws).
|
||||
auto const first = detail::randomU64(engine);
|
||||
BEAST_EXPECT(first == 0xaaaaaaaaaaaaaaaaULL);
|
||||
BEAST_EXPECT(engine.calls == 97);
|
||||
BEAST_EXPECT(first == reference());
|
||||
BEAST_EXPECT(engine.calls == reference.base().calls);
|
||||
|
||||
for (int i = 0; i < 16; ++i)
|
||||
{
|
||||
BEAST_EXPECT(detail::randomU64(engine) == reference());
|
||||
BEAST_EXPECT(engine.calls == reference.base().calls);
|
||||
}
|
||||
|
||||
// Also exercise the public closed-range mapper with the same
|
||||
// normalized stream, including the runtime fault-hook range.
|
||||
for (auto const count : {10u, 10'000u})
|
||||
{
|
||||
auto const n = static_cast<std::uint64_t>(count);
|
||||
auto const slack = static_cast<std::uint64_t>(-n) % n;
|
||||
std::uint64_t expected;
|
||||
do
|
||||
{
|
||||
expected = reference();
|
||||
} while (expected < slack);
|
||||
BEAST_EXPECT(rand_int(engine, 0u, count - 1) == expected % n);
|
||||
BEAST_EXPECT(engine.calls == reference.base().calls);
|
||||
}
|
||||
};
|
||||
|
||||
check(ThreeValues<std::uint32_t>{});
|
||||
check(ThreeValues<std::uint64_t>{});
|
||||
check(ThreeValues<std::uint32_t, 5>{});
|
||||
check(ThreeValues<
|
||||
std::uint64_t,
|
||||
std::numeric_limits<std::uint64_t>::max() - 2>{});
|
||||
}
|
||||
|
||||
void
|
||||
testBounds()
|
||||
{
|
||||
testcase("closed bounds, including signed endpoints and 2^40");
|
||||
|
||||
auto check = [&](auto engine) {
|
||||
expectInRange(engine, 0, 10, 64);
|
||||
expectInRange(engine, 0, 9999, 64);
|
||||
expectInRange(engine, -20, 20, 64);
|
||||
expectInRange(engine, -5, 15, 64);
|
||||
expectInRange(
|
||||
engine, std::uint64_t{0}, (std::uint64_t{1} << 40) + 123u, 8);
|
||||
};
|
||||
|
||||
check(beast::xor_shift_engine{7});
|
||||
check(Bits32{});
|
||||
check(Bits32In64{});
|
||||
check(NonzeroMin{});
|
||||
check(std::minstd_rand{7});
|
||||
check(std::mt19937{7});
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testReducedMapping();
|
||||
testEngineVectors();
|
||||
testZeroBitGroup();
|
||||
testBounds();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(random, basics, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
Reference in New Issue
Block a user