Files
xahaud/src/test/basics/random_test.cpp
2026-09-23 16:12:23 +07:00

300 lines
8.6 KiB
C++

//------------------------------------------------------------------------------
/*
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