From: Jesse Williamson Date: Thu, 1 Mar 2018 14:11:03 +0000 (-0800) Subject: Extends random.h: numeric types relaxed to compatible types (with X-Git-Tag: v13.1.0~282^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=337e98b938ff15af51df404ef24df185533d5c2a;p=ceph.git Extends random.h: numeric types relaxed to compatible types (with appropriate distributions), fixes 2-parameter calling convention; OOP interface now publishes engine and device types and allows appropriate seed types. These changes are the result of direct experience porting Ceph code away from rand(), and a prerequisite for that further porting. Signed-off-by: Jesse Williamson --- diff --git a/src/include/random.h b/src/include/random.h index b985606bdba7b..63593e8e601a7 100644 --- a/src/include/random.h +++ b/src/include/random.h @@ -17,23 +17,72 @@ #include #include +#include #include -#include "boost/optional.hpp" - // Basic random number facility, adapted from N3551: -namespace ceph { -namespace util { +namespace ceph::util { + +inline namespace version_1_0_2 { + +namespace detail { -inline namespace version_1_0 { +template +using larger_of = typename std::conditional< + sizeof(T0) >= sizeof(T1), + T0, T1> + ::type; + +// avoid mixing floating point and integers: +template +using has_compatible_numeric_types = + std::disjunction< + std::conjunction< + std::is_floating_point, std::is_floating_point + >, + std::conjunction< + std::is_integral, std::is_integral + > + >; + + +// Select the larger of type compatible numeric types: +template +using select_number_t = std::enable_if_t::value, + detail::larger_of>; + +} // namespace detail + +namespace detail { + +// Choose default distribution for appropriate types: +template +struct select_distribution +{ + using type = std::uniform_int_distribution; +}; + +template +struct select_distribution +{ + using type = std::uniform_real_distribution; +}; + +template +using default_distribution = typename + select_distribution::value>::type; + +} // namespace detail namespace detail { template EngineT& engine(); -template -void randomize_rng(const int seed, MutexT& m, EngineT& e) +template +void randomize_rng(const SeedT seed, MutexT& m, EngineT& e) { std::lock_guard lg(m); e.seed(seed); @@ -48,8 +97,9 @@ void randomize_rng(MutexT& m, EngineT& e) e.seed(rd()); } -template -void randomize_rng(const int n) +template +void randomize_rng(const SeedT n) { detail::engine().seed(n); } @@ -64,7 +114,7 @@ void randomize_rng() template EngineT& engine() { - thread_local boost::optional rng_engine; + thread_local std::optional rng_engine; if (!rng_engine) { rng_engine.emplace(EngineT()); @@ -79,12 +129,12 @@ EngineT& engine() namespace detail { template , typename EngineT> NumberT generate_random_number(const NumberT min, const NumberT max, EngineT& e) { - thread_local DistributionT d { min, max }; + DistributionT d { min, max }; using param_type = typename DistributionT::param_type; return d(e, param_type { min, max }); @@ -92,12 +142,12 @@ NumberT generate_random_number(const NumberT min, const NumberT max, template , typename EngineT> NumberT generate_random_number(const NumberT min, const NumberT max, MutexT& m, EngineT& e) { - thread_local DistributionT d { min, max }; + DistributionT d { min, max }; using param_type = typename DistributionT::param_type; @@ -106,7 +156,7 @@ NumberT generate_random_number(const NumberT min, const NumberT max, } template , typename EngineT> NumberT generate_random_number(const NumberT min, const NumberT max) { @@ -114,14 +164,20 @@ NumberT generate_random_number(const NumberT min, const NumberT max) (min, max, detail::engine()); } -template ::max(), - typename DistributionT = std::uniform_int_distribution> -int generate_random_number(MutexT& m, EngineT& e) +template > +NumberT generate_random_number(MutexT& m, EngineT& e) { - return detail::generate_random_number - (min, max, m, e); + return detail::generate_random_number + (0, std::numeric_limits::max(), m, e); +} + +template +NumberT generate_random_number(const NumberT max, MutexT& m, EngineT& e) +{ + return generate_random_number(0, max, m, e); } } // namespace detail @@ -132,105 +188,43 @@ void randomize_rng() detail::randomize_rng(); } -template , +template , typename EngineT = std::default_random_engine> -IntegerT generate_random_number() -{ - using limits = std::numeric_limits; - return detail::generate_random_number - (limits::min(), limits::max()); -} - -template -IntegerT generate_random_number(const IntegerT min, const IntegerT max, - std::enable_if_t::value>* = nullptr) -{ - return detail::generate_random_number, - std::default_random_engine> - (min, max); -} - -namespace detail { - -template -int generate_random_number(const IntegerT min, const IntegerT max, - MutexT& m, EngineT& e, - std::enable_if_t::value>* = nullptr) -{ - return detail::generate_random_number, - EngineT> - (min, max, m, e); -} - -template -int generate_random_number(const IntegerT max, - MutexT& m, EngineT& e, - std::enable_if_t::value>* = nullptr) +NumberT generate_random_number() { - constexpr IntegerT zero = 0; - return generate_random_number(zero, max, m, e); -} - -} // namespace detail - -template -int generate_random_number(const IntegerT max, - std::enable_if_t::value>* = nullptr) -{ - constexpr IntegerT zero = 0; - return generate_random_number(zero, max); + return detail::generate_random_number + (0, std::numeric_limits::max()); } -template -RealT generate_random_number(const RealT min, const RealT max, - std::enable_if_t::value>* = nullptr) +template + > +NumberT generate_random_number(const NumberT0 min, const NumberT1 max) { - return detail::generate_random_number, + return detail::generate_random_number, std::default_random_engine> - (min, max); -} - -namespace detail { - -template -RealT generate_random_number(const RealT max, MutexT& m, - std::enable_if_t::value>* = nullptr) -{ - constexpr RealT zero = 0.0; - return generate_random_number(zero, max, m); -} - -template -RealT generate_random_number(const RealT min, const RealT max, MutexT& m, EngineT& e, - std::enable_if_t::value>* = nullptr) -{ - return detail::generate_random_number, - EngineT> - (min, max, m, e); + (static_cast(min), static_cast(max)); } - -template -RealT generate_random_number(const RealT max, MutexT& m, EngineT& e, - std::enable_if_t::value>* = nullptr) +template + > +NumberT generate_random_number(const NumberT min, const NumberT max, + EngineT& e) { - constexpr RealT zero = 0.0; - return generate_random_number(zero, max, m, e); + return detail::generate_random_number(static_cast(min), static_cast(max), e); } -} // namespace detail - -template -RealT generate_random_number(const RealT max, - std::enable_if_t::value>* = nullptr) +template +NumberT generate_random_number(const NumberT max) { - constexpr RealT zero = 0.0; - return generate_random_number(zero, max); + return generate_random_number(0, max); } // Function object: @@ -240,16 +234,24 @@ class random_number_generator final std::mutex l; std::random_device rd; std::default_random_engine e; + + using seed_type = typename decltype(e)::result_type; public: - using number_type = NumberT; + using number_type = NumberT; + using random_engine_type = decltype(e); + using random_device_type = decltype(rd); + + public: + random_device_type& random_device() noexcept { return rd; } + random_engine_type& random_engine() noexcept { return e; } public: random_number_generator() { detail::randomize_rng(l, e); } - explicit random_number_generator(const int seed) { + explicit random_number_generator(const seed_type seed) { detail::randomize_rng(seed, l, e); } @@ -267,21 +269,21 @@ class random_number_generator final } NumberT operator()(const NumberT max) { - return detail::generate_random_number(max, l, e); + return detail::generate_random_number(max, l, e); } NumberT operator()(const NumberT min, const NumberT max) { - return detail::generate_random_number(min, max, l, e); + return detail::generate_random_number(min, max, l, e); } public: - void seed(const int n) { + void seed(const seed_type n) { detail::randomize_rng(n, l, e); } }; -} // inline namespace version_1_0 +} // inline namespace version_* -}} // namespace ceph::util +} // namespace ceph::util #endif diff --git a/src/test/common/test_random.cc b/src/test/common/test_random.cc index 0b8b566af264e..85bb732b4752d 100644 --- a/src/test/common/test_random.cc +++ b/src/test/common/test_random.cc @@ -86,6 +86,25 @@ TEST(util, test_random) ASSERT_NE(a, b); } } + + // Check that the nullary version accepts different numeric types: + { + long def = ceph::util::generate_random_number(); + long l = ceph::util::generate_random_number(); + int64_t i = ceph::util::generate_random_number(); + double d = ceph::util::generate_random_number(); + + swallow_values(def, l, i, d); + } + + // (optimistically) Check that the nullary and unary versions never return < 0: + { + for(long i = 0; 1000000 != i; i++) { + ASSERT_LE(0, ceph::util::generate_random_number()); + ASSERT_LE(0, ceph::util::generate_random_number(1)); + ASSERT_LE(0, ceph::util::generate_random_number(1.0)); + } + } { auto a = ceph::util::generate_random_number(1, std::numeric_limits::max()); @@ -100,11 +119,16 @@ TEST(util, test_random) } for (auto n = 100000; n; --n) { - constexpr int min = 0, max = 6; - int a = ceph::util::generate_random_number(min, max); + int a = ceph::util::generate_random_number(0, 6); ASSERT_GT(a, -1); ASSERT_LT(a, 7); } + + // Check bounding on zero (checking appropriate value for zero compiles and works): + for (auto n = 10; n; --n) { + ASSERT_EQ(0, ceph::util::generate_random_number(0, 0)); + ASSERT_EQ(0, ceph::util::generate_random_number(0.0, 0.0)); + } // Multiple types (integral): { @@ -133,6 +157,37 @@ TEST(util, test_random) float min = 1.0, max = 0.0; type_check_ok(min, max); } + + // When combining types, everything should convert to the largest type: + { + // Check with integral types: + { + int x = 0; + long long y = 1; + + auto z = ceph::util::generate_random_number(x, y); + + bool result = std::is_same_v; + + ASSERT_TRUE(result); + } + + // Check with floating-point types: + { + float x = 0.0; + long double y = 1.0; + + auto z = ceph::util::generate_random_number(x, y); + + bool result = std::is_same_v; + + ASSERT_TRUE(result); + } + + // It would be nice to have a test to check that mixing integral and floating point + // numbers should not compile, however we currently have no good way I know of + // to do such negative tests. + } } TEST(util, test_random_class_interface)