From ae4fe16b81d819bbef8920eddf8d38a3538fe826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ho=C5=99e=C5=88ovsk=C3=BD?= Date: Sun, 10 Dec 2023 16:24:46 +0100 Subject: [PATCH] Make the user-facing random Generators reproducible Thanks to the new distributions, this is almost trivial change. --- docs/faq.md | 11 ++++-- docs/generators.md | 25 ++++++++++++++ .../generators/catch_generators_random.cpp | 32 +++++++++++++++-- .../generators/catch_generators_random.hpp | 34 ++++++++++++------- 4 files changed, 85 insertions(+), 17 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 78da167d..608277e2 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -84,9 +84,14 @@ and it is also generally repeatable across versions, but we might break it from time to time. E.g. we broke repeatability with previous versions in v2.13.4 so that test cases with similar names are shuffled better. -Random generators currently rely on platform's stdlib, specifically -the distributions from ``. We thus provide no extra guarantee -above what your platform does. **Important: ``'s distributions +Since Catch2 vX.Y.Z the random generators use custom distributions, +that should be repeatable across different platforms, with few caveats. +For details see the section on random generators in the [Generator +documentation](generators.md#random-number-generators-details). + +Before this version, random generators relied on distributions from +platform's stdlib. We thus can provide no extra guarantee on top of the +ones given by your platform. **Important: ``'s distributions are not specified to be repeatable across different platforms.** diff --git a/docs/generators.md b/docs/generators.md index 09799752..7929122b 100644 --- a/docs/generators.md +++ b/docs/generators.md @@ -189,6 +189,31 @@ TEST_CASE("type conversion", "[generators]") { } ``` + +### Random number generators: details + +> This section applies from Catch2 vX.Y.Z. Before that, random generators +> were a thin wrapper around distributions from ``. + +All of the `random(a, b)` generators in Catch2 currently generate uniformly +distributed number in closed interval \[a; b\]. This is different from +`std::uniform_real_distribution`, which should return numbers in interval +\[a; b) (but due to rounding can end up returning b anyway), but the +difference is intentional, so that `random(a, a)` makes sense. If there is +enough interest from users, we can provide API to pick any of CC, CO, OC, +or OO ranges. + +Unlike `std::uniform_int_distribution`, Catch2's generators also support +various single-byte integral types, such as `char` or `bool`. + +Given the same seed, the output from the integral generators is +reproducible across different platforms. For floating point generators, +we only promise reproducibility on platforms that obey the IEEE 754 +standard, and where `float` is 4 bytes and `double` is 8 bytes. We provide +no guarantees for `long double`, as the internals of `long double` can +vary wildly across different platforms. + + ## Generator interface You can also implement your own generators, by deriving from the diff --git a/src/catch2/generators/catch_generators_random.cpp b/src/catch2/generators/catch_generators_random.cpp index 2e3390fd..00a8e634 100644 --- a/src/catch2/generators/catch_generators_random.cpp +++ b/src/catch2/generators/catch_generators_random.cpp @@ -7,7 +7,35 @@ // SPDX-License-Identifier: BSL-1.0 #include - #include -std::uint32_t Catch::Generators::Detail::getSeed() { return sharedRng()(); } +#include + +namespace Catch { + namespace Generators { + namespace Detail { + std::uint32_t getSeed() { return sharedRng()(); } + } // namespace Detail + + struct RandomFloatingGenerator::PImpl { + PImpl( long double a, long double b, uint32_t seed ): + rng( seed ), dist( a, b ) {} + + Catch::SimplePcg32 rng; + std::uniform_real_distribution dist; + }; + + RandomFloatingGenerator::RandomFloatingGenerator( + long double a, long double b, std::uint32_t seed) : + m_pimpl(Catch::Detail::make_unique(a, b, seed)) { + static_cast( next() ); + } + + RandomFloatingGenerator::~RandomFloatingGenerator() = + default; + bool RandomFloatingGenerator::next() { + m_current_number = m_pimpl->dist( m_pimpl->rng ); + return true; + } + } // namespace Generators +} // namespace Catch diff --git a/src/catch2/generators/catch_generators_random.hpp b/src/catch2/generators/catch_generators_random.hpp index bcd4888d..d5f307d4 100644 --- a/src/catch2/generators/catch_generators_random.hpp +++ b/src/catch2/generators/catch_generators_random.hpp @@ -11,8 +11,9 @@ #include #include #include - -#include +#include +#include +#include namespace Catch { namespace Generators { @@ -26,7 +27,7 @@ namespace Detail { template class RandomFloatingGenerator final : public IGenerator { Catch::SimplePcg32 m_rng; - std::uniform_real_distribution m_dist; + Catch::uniform_floating_point_distribution m_dist; Float m_current_number; public: RandomFloatingGenerator( Float a, Float b, std::uint32_t seed ): @@ -44,10 +45,27 @@ public: } }; +template <> +class RandomFloatingGenerator final : public IGenerator { + // We still rely on for this specialization, but we don't + // want to drag it into the header. + struct PImpl; + Catch::Detail::unique_ptr m_pimpl; + long double m_current_number; + +public: + RandomFloatingGenerator( long double a, long double b, std::uint32_t seed ); + + long double const& get() const override { return m_current_number; } + bool next() override; + + ~RandomFloatingGenerator() override; // = default +}; + template class RandomIntegerGenerator final : public IGenerator { Catch::SimplePcg32 m_rng; - std::uniform_int_distribution m_dist; + Catch::uniform_integer_distribution m_dist; Integer m_current_number; public: RandomIntegerGenerator( Integer a, Integer b, std::uint32_t seed ): @@ -68,14 +86,6 @@ public: template std::enable_if_t::value, GeneratorWrapper> random(T a, T b) { - static_assert( - !std::is_same::value && - !std::is_same::value && - !std::is_same::value && - !std::is_same::value && - !std::is_same::value && - !std::is_same::value, - "The requested type is not supported by the underlying random distributions from std" ); return GeneratorWrapper( Catch::Detail::make_unique>(a, b, Detail::getSeed()) );