mirror of
				https://github.com/catchorg/Catch2.git
				synced 2025-10-31 20:27:11 +01:00 
			
		
		
		
	Introduce Catch's own RNG based on the PCG family of RNGs
In the future, we will also want to introduce our own `uniform_int_distribution` and `uniform_real_distribution` to get repeatable test runs across different platforms.
This commit is contained in:
		| @@ -7,6 +7,7 @@ | ||||
|  */ | ||||
| #include "catch_context.h" | ||||
| #include "catch_common.h" | ||||
| #include "catch_random_number_generator.h" | ||||
|  | ||||
| namespace Catch { | ||||
|  | ||||
| @@ -59,4 +60,11 @@ namespace Catch { | ||||
|     IContext::~IContext() = default; | ||||
|     IMutableContext::~IMutableContext() = default; | ||||
|     Context::~Context() = default; | ||||
|  | ||||
|  | ||||
|     SimplePcg32& rng() { | ||||
|         static SimplePcg32 s_rng; | ||||
|         return s_rng; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -55,6 +55,9 @@ namespace Catch { | ||||
|     } | ||||
|  | ||||
|     void cleanUpContext(); | ||||
|  | ||||
|     class SimplePcg32; | ||||
|     SimplePcg32& rng(); | ||||
| } | ||||
|  | ||||
| #endif // TWOBLUECUBES_CATCH_CONTEXT_H_INCLUDED | ||||
|   | ||||
| @@ -7,23 +7,67 @@ | ||||
|  | ||||
| #include "catch_random_number_generator.h" | ||||
| #include "catch_context.h" | ||||
| #include "catch_run_context.h" | ||||
| #include "catch_interfaces_config.h" | ||||
|  | ||||
| namespace Catch { | ||||
|  | ||||
|     std::mt19937& rng() { | ||||
|         static std::mt19937 s_rng; | ||||
|         return s_rng; | ||||
| namespace { | ||||
|  | ||||
| #if defined(_MSC_VER) | ||||
| #pragma warning(push) | ||||
| #pragma warning(disable:4146) // we negate uint32 during the rotate | ||||
| #endif | ||||
|         // Safe rotr implementation thanks to John Regehr | ||||
|         uint32_t rotate_right(uint32_t val, uint32_t count) { | ||||
|             const uint32_t mask = 31; | ||||
|             count &= mask; | ||||
|             return (val >> count) | (val << (-count & mask)); | ||||
|         } | ||||
|  | ||||
| #if defined(_MSC_VER) | ||||
| #pragma warning(pop) | ||||
| #endif | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
|     SimplePcg32::SimplePcg32(result_type seed_) { | ||||
|         seed(seed_); | ||||
|     } | ||||
|  | ||||
|     void seedRng( IConfig const& config ) { | ||||
|         if( config.rngSeed() != 0 ) { | ||||
|             std::srand( config.rngSeed() ); | ||||
|             rng().seed( config.rngSeed() ); | ||||
|  | ||||
|     void SimplePcg32::seed(result_type seed_) { | ||||
|         m_state = 0; | ||||
|         (*this)(); | ||||
|         m_state += seed_; | ||||
|         (*this)(); | ||||
|     } | ||||
|  | ||||
|     void SimplePcg32::discard(uint64_t skip) { | ||||
|         // We could implement this to run in O(log n) steps, but this | ||||
|         // should suffice for our use case. | ||||
|         for (uint64_t s = 0; s < skip; ++s) { | ||||
|             static_cast<void>((*this)()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     unsigned int rngSeed() { | ||||
|         return getCurrentContext().getConfig()->rngSeed(); | ||||
|     SimplePcg32::result_type SimplePcg32::operator()() { | ||||
|         // prepare the output value | ||||
|         const uint32_t xorshifted = static_cast<uint32_t>(((m_state >> 18u) ^ m_state) >> 27u); | ||||
|         const auto output = rotate_right(xorshifted, m_state >> 59u); | ||||
|  | ||||
|         // advance state | ||||
|         m_state = m_state * 6364136223846793005ULL + s_inc; | ||||
|  | ||||
|         return output; | ||||
|     } | ||||
|  | ||||
|     bool operator==(SimplePcg32 const& lhs, SimplePcg32 const& rhs) { | ||||
|         return lhs.m_state == rhs.m_state; | ||||
|     } | ||||
|  | ||||
|     bool operator!=(SimplePcg32 const& lhs, SimplePcg32 const& rhs) { | ||||
|         return lhs.m_state != rhs.m_state; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -7,17 +7,52 @@ | ||||
| #ifndef TWOBLUECUBES_CATCH_RANDOM_NUMBER_GENERATOR_H_INCLUDED | ||||
| #define TWOBLUECUBES_CATCH_RANDOM_NUMBER_GENERATOR_H_INCLUDED | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <random> | ||||
| #include <cstdint> | ||||
|  | ||||
| namespace Catch { | ||||
|  | ||||
|     struct IConfig; | ||||
|     // This is a simple implementation of C++11 Uniform Random Number | ||||
|     // Generator. It does not provide all operators, because Catch2 | ||||
|     // does not use it, but it should behave as expected inside stdlib's | ||||
|     // distributions. | ||||
|     // The implementation is based on the PCG family (http://pcg-random.org) | ||||
|     class SimplePcg32 { | ||||
|         using state_type = std::uint64_t; | ||||
|     public: | ||||
|         using result_type = std::uint32_t; | ||||
|         static constexpr result_type min() { | ||||
|             return 0; | ||||
|         } | ||||
|         static constexpr result_type max() { | ||||
|             return static_cast<result_type>(-1); | ||||
|         } | ||||
|  | ||||
|     std::mt19937& rng(); | ||||
|     void seedRng( IConfig const& config ); | ||||
|     unsigned int rngSeed(); | ||||
|         // Provide some default initial state for the default constructor | ||||
|         SimplePcg32():SimplePcg32(0xed743cc4U) {} | ||||
|  | ||||
| } | ||||
|         explicit SimplePcg32(result_type seed_); | ||||
|  | ||||
|         void seed(result_type seed_); | ||||
|         void discard(uint64_t skip); | ||||
|  | ||||
|         result_type operator()(); | ||||
|  | ||||
|     private: | ||||
|         friend bool operator==(SimplePcg32 const& lhs, SimplePcg32 const& rhs); | ||||
|         friend bool operator!=(SimplePcg32 const& lhs, SimplePcg32 const& rhs); | ||||
|  | ||||
|         // In theory we also need operator<< and operator>> | ||||
|         // In practice we do not use them, so we will skip them for now | ||||
|  | ||||
|  | ||||
|         std::uint64_t m_state; | ||||
|         // This part of the state determines which "stream" of the numbers | ||||
|         // is chosen -- we take it as a constant for Catch2, so we only | ||||
|         // need to deal with seeding the main state. | ||||
|         // Picked by reading 8 bytes from `/dev/random` :-) | ||||
|         static const std::uint64_t s_inc = (0x13ed0cc53f939476ULL << 1ULL) | 1ULL; | ||||
|     }; | ||||
|  | ||||
| } // end namespace Catch | ||||
|  | ||||
| #endif // TWOBLUECUBES_CATCH_RANDOM_NUMBER_GENERATOR_H_INCLUDED | ||||
|   | ||||
| @@ -506,4 +506,16 @@ namespace Catch { | ||||
|         else | ||||
|             CATCH_INTERNAL_ERROR("No result capture instance"); | ||||
|     } | ||||
|  | ||||
|     void seedRng(IConfig const& config) { | ||||
|         if (config.rngSeed() != 0) { | ||||
|             std::srand(config.rngSeed()); | ||||
|             rng().seed(config.rngSeed()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     unsigned int rngSeed() { | ||||
|         return getCurrentContext().getConfig()->rngSeed(); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -151,6 +151,8 @@ namespace Catch { | ||||
|         bool m_includeSuccessfulResults; | ||||
|     }; | ||||
|  | ||||
|     void seedRng(IConfig const& config); | ||||
|     unsigned int rngSeed(); | ||||
| } // end namespace Catch | ||||
|  | ||||
| #endif // TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED | ||||
|   | ||||
| @@ -11,6 +11,7 @@ | ||||
| #include "catch_enforce.h" | ||||
| #include "catch_interfaces_registry_hub.h" | ||||
| #include "catch_random_number_generator.h" | ||||
| #include "catch_run_context.h" | ||||
| #include "catch_string_manip.h" | ||||
| #include "catch_test_case_info.h" | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Martin Hořeňovský
					Martin Hořeňovský