From 107f57961b71e54ae7be6b25866e453ab4804035 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 25 Mar 2026 11:49:43 -0400 Subject: [PATCH 01/16] Add specialization of uniform int distribution for safe numbers --- include/boost/safe_numbers/random.hpp | 73 +++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 include/boost/safe_numbers/random.hpp diff --git a/include/boost/safe_numbers/random.hpp b/include/boost/safe_numbers/random.hpp new file mode 100644 index 0000000..431fa06 --- /dev/null +++ b/include/boost/safe_numbers/random.hpp @@ -0,0 +1,73 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_SAFE_NUMBERS_RANDOM_HPP +#define BOOST_SAFE_NUMBERS_RANDOM_HPP + +#include +#include +#include +#include +#include +#include +#include + +#ifndef BOOST_SAFE_NUMBERS_BUILD_MODULE + +#include + +#endif + +namespace boost::random { + +template + requires (safe_numbers::detail::is_unsigned_library_type_v || + safe_numbers::detail::is_signed_library_type_v) +class uniform_int_distribution +{ +private: + using underlying_t = safe_numbers::detail::underlying_type_t; + + underlying_t _min; + underlying_t _max; + boost::random::uniform_int_distribution _dist; + +public: + + explicit uniform_int_distribution( + const SafeT min_arg = (std::numeric_limits::min)(), + const SafeT max_arg = (std::numeric_limits::max)()) + : _min{static_cast(min_arg)} + , _max{static_cast(max_arg)} + , _dist{_min, _max} + { + BOOST_ASSERT(min_arg <= max_arg); + } + + template + auto operator()(Engine& eng) const -> SafeT + { + return SafeT{_dist(eng)}; + } + + auto min BOOST_PREVENT_MACRO_SUBSTITUTION () const -> SafeT { return SafeT{_min}; } + auto max BOOST_PREVENT_MACRO_SUBSTITUTION () const -> SafeT { return SafeT{_max}; } + + auto a() const -> SafeT { return SafeT{_min}; } + auto b() const -> SafeT { return SafeT{_max}; } + + friend bool operator==(const uniform_int_distribution& lhs, const uniform_int_distribution& rhs) + { + return lhs._min == rhs._min && lhs._max == rhs._max; + } + + friend bool operator!=(const uniform_int_distribution& lhs, const uniform_int_distribution& rhs) + { + return lhs._min != rhs._min || lhs._max != rhs._max; + } +}; + +} // namespace boost::random + +#endif // BOOST_SAFE_NUMBERS_RANDOM_HPP From 02075fb94061bb6759d308f0fd95c36f66f56d65 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 25 Mar 2026 11:50:07 -0400 Subject: [PATCH 02/16] Allow int128 sign conversions internally --- include/boost/safe_numbers.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/boost/safe_numbers.hpp b/include/boost/safe_numbers.hpp index 8b746cd..2727323 100644 --- a/include/boost/safe_numbers.hpp +++ b/include/boost/safe_numbers.hpp @@ -5,6 +5,8 @@ #ifndef BOOST_SAFENUMBERS_HPP #define BOOST_SAFENUMBERS_HPP +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_CONVERSION + #include #include #include @@ -18,4 +20,6 @@ #include #include +#undef BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_CONVERSION + #endif //BOOST_SAFENUMBERS_HPP From 781deed0fa30ec81347e5d494b9c21d3dc81ca53 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 25 Mar 2026 11:50:21 -0400 Subject: [PATCH 03/16] Add testing of unsigned random --- test/Jamfile | 3 +++ test/test_unsigned_random.cpp | 48 +++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 test/test_unsigned_random.cpp diff --git a/test/Jamfile b/test/Jamfile index 4fd2304..5f6b8f1 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -142,6 +142,9 @@ run test_abs_diff.cpp ; run test_div_ceil.cpp ; run test_next_multiple_of.cpp ; +# Random Number tests +run test_unsigned_random.cpp ; + # Compile Tests compile compile_tests/compile_test_unsigned_integers.cpp ; compile compile_tests/compile_test_iostream.cpp ; diff --git a/test/test_unsigned_random.cpp b/test/test_unsigned_random.cpp new file mode 100644 index 0000000..c8f8d12 --- /dev/null +++ b/test/test_unsigned_random.cpp @@ -0,0 +1,48 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include +#include +#include +#include + +#ifdef BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_CONVERSION +# error "Should not be defined for the user" +#endif + +using namespace boost::safe_numbers; + +static std::mt19937_64 rng{42}; + +template +void test() +{ + boost::random::uniform_int_distribution dist{std::numeric_limits::min(), std::numeric_limits::max()}; + const auto val {dist(rng)}; + BOOST_TEST_GE(val, std::numeric_limits::min()); + BOOST_TEST_LE(val, std::numeric_limits::max()); + + BOOST_TEST_GE(val, dist.min()); + BOOST_TEST_LE(val, dist.max()); + + BOOST_TEST_GE(val, dist.a()); + BOOST_TEST_LE(val, dist.b()); + + auto dist2 = dist; + + BOOST_TEST(dist2 == dist); + BOOST_TEST(!(dist2 != dist)); +} + +int main() +{ + test(); + test(); + test(); + test(); + test(); + + return boost::report_errors(); +} \ No newline at end of file From ac1cf4a4ae9d08b9fd3fe6c2f3c5b075980485cb Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 25 Mar 2026 11:54:45 -0400 Subject: [PATCH 04/16] Add reference to p2993 constrained numbers --- doc/modules/ROOT/pages/reference.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/modules/ROOT/pages/reference.adoc b/doc/modules/ROOT/pages/reference.adoc index 2b5e25c..4b66828 100644 --- a/doc/modules/ROOT/pages/reference.adoc +++ b/doc/modules/ROOT/pages/reference.adoc @@ -21,3 +21,4 @@ The following books: papers and blog posts serve as the basis for the algorithms 7. S. Glesner, “Finite Integer Computations: An Algebraic Foundation for Their Correctness,” Formal Aspects of Computing, vol. 18, no. 2, pp. 244-262, 2006. 8. R. Rieu-Helft, C. Marché, and G. Melquiond, “How to Get an Efficient yet Verified Arbitrary-Precision Integer Library,” in Verified Software. Theories, Tools, and Experiments (VSTTE 2017), A. Paskevich and T. Wies, Eds. Lecture Notes in Computer Science, vol. 10712. Cham, Switzerland: Springer, 2017, pp. 84-101. 9. G. Melquiond and R. Rieu-Helft, "A Why3 framework for reflection proofs and its application to GMP's algorithms," in Proc. 9th Int. Joint Conf. Automated Reasoning (IJCAR), Oxford, UK, Jul. 2018, vol. 10900, pp. 178–193. +10. L. Valenty, "P2993 Constrained Numbers," https://wg21.link/p2993 From 01e33216749412e38b28a01fb0cf796e52640989 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 25 Mar 2026 12:03:55 -0400 Subject: [PATCH 05/16] Add augmented param_type class --- include/boost/safe_numbers/random.hpp | 58 +++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/include/boost/safe_numbers/random.hpp b/include/boost/safe_numbers/random.hpp index 431fa06..0ba977f 100644 --- a/include/boost/safe_numbers/random.hpp +++ b/include/boost/safe_numbers/random.hpp @@ -35,6 +35,64 @@ class uniform_int_distribution public: + class param_type + { + private: + + SafeT _min; + SafeT _max; + + public: + + explicit param_type( + const SafeT min_arg = (std::numeric_limits::min)(), + const SafeT max_arg = (std::numeric_limits::max)()) + : _min{static_cast(min_arg)}, _max{static_cast(max_arg)} + { + BOOST_ASSERT(min_arg <= max_arg); + } + + auto a() const -> SafeT { return _min; } + auto b() const -> SafeT { return _max; } + + BOOST_RANDOM_DETAIL_OSTREAM_OPERATOR(os, param_type, param) + { + os << param._min << " " << param._max; + return os; + } + + BOOST_RANDOM_DETAIL_ISTREAM_OPERATOR(is, param_type, param) + { + SafeT min_in; + SafeT max_in; + + if (is >> min_in >> std::ws >> max_in) + { + if (min_in <= max_in) + { + param._min = min_in; + param._max = max_in; + } + else + { + is.setstate(std::ios_base::failbit); + } + } + + return is; + } + + friend bool operator==(const param_type& lhs, const param_type& rhs) + { + return lhs._min == rhs._min && lhs._max == rhs._max; + } + + friend bool operator!=(const param_type& lhs, const param_type& rhs) + { + return lhs._min != rhs._min || lhs._max != rhs._max; + } + }; + explicit uniform_int_distribution( const SafeT min_arg = (std::numeric_limits::min)(), const SafeT max_arg = (std::numeric_limits::max)()) From 9a48735811c474b5d9be5e2f553d4dd47d0d59c0 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 25 Mar 2026 12:19:04 -0400 Subject: [PATCH 06/16] Add additional operators to make conforming impl --- include/boost/safe_numbers/random.hpp | 62 +++++++++++++++++++-------- 1 file changed, 45 insertions(+), 17 deletions(-) diff --git a/include/boost/safe_numbers/random.hpp b/include/boost/safe_numbers/random.hpp index 0ba977f..37694b0 100644 --- a/include/boost/safe_numbers/random.hpp +++ b/include/boost/safe_numbers/random.hpp @@ -82,32 +82,24 @@ class uniform_int_distribution return is; } - friend bool operator==(const param_type& lhs, const param_type& rhs) + BOOST_RANDOM_DETAIL_EQUALITY_OPERATOR(param_type, lhs, rhs) { return lhs._min == rhs._min && lhs._max == rhs._max; } - friend bool operator!=(const param_type& lhs, const param_type& rhs) - { - return lhs._min != rhs._min || lhs._max != rhs._max; - } + BOOST_RANDOM_DETAIL_INEQUALITY_OPERATOR(param_type) }; explicit uniform_int_distribution( const SafeT min_arg = (std::numeric_limits::min)(), const SafeT max_arg = (std::numeric_limits::max)()) - : _min{static_cast(min_arg)} - , _max{static_cast(max_arg)} - , _dist{_min, _max} + : _min{static_cast(min_arg)}, _max{static_cast(max_arg)}, _dist{_min, _max} { BOOST_ASSERT(min_arg <= max_arg); } - template - auto operator()(Engine& eng) const -> SafeT - { - return SafeT{_dist(eng)}; - } + explicit uniform_int_distribution(const param_type& param) + : _min{static_cast(param._min)}, _max{static_cast(param._max)} {} auto min BOOST_PREVENT_MACRO_SUBSTITUTION () const -> SafeT { return SafeT{_min}; } auto max BOOST_PREVENT_MACRO_SUBSTITUTION () const -> SafeT { return SafeT{_max}; } @@ -115,15 +107,51 @@ class uniform_int_distribution auto a() const -> SafeT { return SafeT{_min}; } auto b() const -> SafeT { return SafeT{_max}; } - friend bool operator==(const uniform_int_distribution& lhs, const uniform_int_distribution& rhs) + param_type param() const { return param_type{SafeT{_min}, SafeT{_max}};} + + void param(const param_type& param) { - return lhs._min == rhs._min && lhs._max == rhs._max; + _min = static_cast(param.a()); + _max = static_cast(param.b()); } - friend bool operator!=(const uniform_int_distribution& lhs, const uniform_int_distribution& rhs) + void reset() { } + + template + auto operator()(Engine& eng) const -> SafeT + { + return SafeT{detail::generate_uniform_int(eng, _min, _max)}; + } + + template + auto operator()(Engine& eng, const param_type& param) const -> SafeT { - return lhs._min != rhs._min || lhs._max != rhs._max; + return SafeT{detail::generate_uniform_int(eng, static_cast(param.a()), static_cast(param.b()))}; } + + BOOST_RANDOM_DETAIL_OSTREAM_OPERATOR(os, uniform_int_distribution, ud) + { + os << ud.param(); + return os; + } + + BOOST_RANDOM_DETAIL_ISTREAM_OPERATOR(is, uniform_int_distribution, ud) + { + param_type param; + if (is >> param) + { + ud.param(param); + } + + return is; + } + + BOOST_RANDOM_DETAIL_EQUALITY_OPERATOR(uniform_int_distribution, lhs, rhs) + { + return lhs._min == rhs._min && lhs._max == rhs._max; + } + + BOOST_RANDOM_DETAIL_INEQUALITY_OPERATOR(uniform_int_distribution) }; } // namespace boost::random From fc6b35e95e5cb3207cf48a6391694f69e4775715 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 25 Mar 2026 12:25:54 -0400 Subject: [PATCH 07/16] Add testing of remaining operators --- test/test_unsigned_random.cpp | 235 +++++++++++++++++++++++++++++++--- 1 file changed, 217 insertions(+), 18 deletions(-) diff --git a/test/test_unsigned_random.cpp b/test/test_unsigned_random.cpp index c8f8d12..07f215e 100644 --- a/test/test_unsigned_random.cpp +++ b/test/test_unsigned_random.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #ifdef BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_CONVERSION # error "Should not be defined for the user" @@ -17,32 +18,230 @@ using namespace boost::safe_numbers; static std::mt19937_64 rng{42}; template -void test() +void test_default_construction() { - boost::random::uniform_int_distribution dist{std::numeric_limits::min(), std::numeric_limits::max()}; - const auto val {dist(rng)}; - BOOST_TEST_GE(val, std::numeric_limits::min()); - BOOST_TEST_LE(val, std::numeric_limits::max()); + boost::random::uniform_int_distribution dist; + BOOST_TEST_EQ(dist.a(), (std::numeric_limits::min)()); + BOOST_TEST_EQ(dist.b(), (std::numeric_limits::max)()); + BOOST_TEST_EQ(dist.min(), (std::numeric_limits::min)()); + BOOST_TEST_EQ(dist.max(), (std::numeric_limits::max)()); +} + +template +void test_range_construction() +{ + auto lo = T{1}; + auto hi = T{10}; + boost::random::uniform_int_distribution dist{lo, hi}; + BOOST_TEST_EQ(dist.a(), lo); + BOOST_TEST_EQ(dist.b(), hi); + BOOST_TEST_EQ(dist.min(), lo); + BOOST_TEST_EQ(dist.max(), hi); +} + +template +void test_generation() +{ + auto lo = T{1}; + auto hi = T{100}; + boost::random::uniform_int_distribution dist{lo, hi}; + + for (int i {}; i < 1000; ++i) + { + auto val = dist(rng); + BOOST_TEST_GE(val, lo); + BOOST_TEST_LE(val, hi); + } +} + +template +void test_generation_with_param() +{ + using dist_t = boost::random::uniform_int_distribution; + using param_t = typename dist_t::param_type; + + dist_t dist; + auto lo = T{5}; + auto hi = T{50}; + param_t p{lo, hi}; + + for (int i {}; i < 100; ++i) + { + auto val = dist(rng, p); + BOOST_TEST_GE(val, lo); + BOOST_TEST_LE(val, hi); + } +} + +template +void test_param_type() +{ + using dist_t = boost::random::uniform_int_distribution; + using param_t = typename dist_t::param_type; - BOOST_TEST_GE(val, dist.min()); - BOOST_TEST_LE(val, dist.max()); + // Default construction + param_t p_default; + BOOST_TEST_EQ(p_default.a(), (std::numeric_limits::min)()); + BOOST_TEST_EQ(p_default.b(), (std::numeric_limits::max)()); - BOOST_TEST_GE(val, dist.a()); - BOOST_TEST_LE(val, dist.b()); + // Range construction + auto lo = T{2}; + auto hi = T{20}; + param_t p{lo, hi}; + BOOST_TEST_EQ(p.a(), lo); + BOOST_TEST_EQ(p.b(), hi); - auto dist2 = dist; + // Equality / inequality + param_t p2{lo, hi}; + BOOST_TEST(p == p2); + BOOST_TEST(!(p != p2)); + BOOST_TEST(p != p_default); + BOOST_TEST(!(p == p_default)); +} - BOOST_TEST(dist2 == dist); - BOOST_TEST(!(dist2 != dist)); +template +void test_param_construction() +{ + using dist_t = boost::random::uniform_int_distribution; + using param_t = typename dist_t::param_type; + + auto lo = T{3}; + auto hi = T{30}; + param_t p{lo, hi}; + + dist_t dist{p}; + BOOST_TEST_EQ(dist.a(), lo); + BOOST_TEST_EQ(dist.b(), hi); +} + +template +void test_param_getter_setter() +{ + using dist_t = boost::random::uniform_int_distribution; + using param_t = typename dist_t::param_type; + + auto lo = T{1}; + auto hi = T{10}; + dist_t dist{lo, hi}; + + // Getter + auto p = dist.param(); + BOOST_TEST_EQ(p.a(), lo); + BOOST_TEST_EQ(p.b(), hi); + + // Setter + auto lo2 = T{5}; + auto hi2 = T{50}; + param_t p2{lo2, hi2}; + dist.param(p2); + BOOST_TEST_EQ(dist.a(), lo2); + BOOST_TEST_EQ(dist.b(), hi2); +} + +template +void test_reset() +{ + boost::random::uniform_int_distribution dist; + dist.reset(); + auto val = dist(rng); + BOOST_TEST_GE(val, (std::numeric_limits::min)()); + BOOST_TEST_LE(val, (std::numeric_limits::max)()); +} + +template +void test_equality() +{ + auto lo = T{1}; + auto hi = T{10}; + boost::random::uniform_int_distribution dist1{lo, hi}; + boost::random::uniform_int_distribution dist2{lo, hi}; + boost::random::uniform_int_distribution dist3{lo, T{20}}; + + BOOST_TEST(dist1 == dist2); + BOOST_TEST(!(dist1 != dist2)); + BOOST_TEST(dist1 != dist3); + BOOST_TEST(!(dist1 == dist3)); +} + +template +void test_dist_stream_roundtrip() +{ + auto lo = T{3}; + auto hi = T{42}; + boost::random::uniform_int_distribution dist{lo, hi}; + + std::stringstream ss; + ss << dist; + + boost::random::uniform_int_distribution dist2; + ss >> dist2; + + BOOST_TEST(dist == dist2); + BOOST_TEST_EQ(dist2.a(), lo); + BOOST_TEST_EQ(dist2.b(), hi); +} + +template +void test_param_stream_roundtrip() +{ + using dist_t = boost::random::uniform_int_distribution; + using param_t = typename dist_t::param_type; + + auto lo = T{7}; + auto hi = T{77}; + param_t p{lo, hi}; + + std::stringstream ss; + ss << p; + + param_t p2; + ss >> p2; + + BOOST_TEST(p == p2); + BOOST_TEST_EQ(p2.a(), lo); + BOOST_TEST_EQ(p2.b(), hi); +} + +template +void test_param_stream_invalid() +{ + using dist_t = boost::random::uniform_int_distribution; + using param_t = typename dist_t::param_type; + + // Write max < min (invalid) + std::stringstream ss; + ss << T{50} << " " << T{5}; + + param_t p; + ss >> p; + + BOOST_TEST(ss.fail()); +} + +template +void test_all() +{ + test_default_construction(); + test_range_construction(); + test_generation(); + test_generation_with_param(); + test_param_type(); + test_param_construction(); + test_param_getter_setter(); + test_reset(); + test_equality(); + test_dist_stream_roundtrip(); + test_param_stream_roundtrip(); + test_param_stream_invalid(); } int main() { - test(); - test(); - test(); - test(); - test(); + test_all(); + test_all(); + test_all(); + test_all(); + test_all(); return boost::report_errors(); -} \ No newline at end of file +} From a0f591d410f0c8e744b30518f739ac09d744234e Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 25 Mar 2026 12:26:04 -0400 Subject: [PATCH 08/16] Fix access to _min and _max --- include/boost/safe_numbers/random.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/safe_numbers/random.hpp b/include/boost/safe_numbers/random.hpp index 37694b0..c0eb693 100644 --- a/include/boost/safe_numbers/random.hpp +++ b/include/boost/safe_numbers/random.hpp @@ -99,7 +99,7 @@ class uniform_int_distribution } explicit uniform_int_distribution(const param_type& param) - : _min{static_cast(param._min)}, _max{static_cast(param._max)} {} + : _min{static_cast(param.a())}, _max{static_cast(param.b())}, _dist{_min, _max} {} auto min BOOST_PREVENT_MACRO_SUBSTITUTION () const -> SafeT { return SafeT{_min}; } auto max BOOST_PREVENT_MACRO_SUBSTITUTION () const -> SafeT { return SafeT{_max}; } From d6e38228b7421156df044240c280e87c44058334 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 25 Mar 2026 12:26:19 -0400 Subject: [PATCH 09/16] Apply same testing to signed integers --- test/Jamfile | 1 + test/test_signed_random.cpp | 184 ++++++++++++++++++++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 test/test_signed_random.cpp diff --git a/test/Jamfile b/test/Jamfile index 5f6b8f1..82dca58 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -144,6 +144,7 @@ run test_next_multiple_of.cpp ; # Random Number tests run test_unsigned_random.cpp ; +run test_signed_random.cpp ; # Compile Tests compile compile_tests/compile_test_unsigned_integers.cpp ; diff --git a/test/test_signed_random.cpp b/test/test_signed_random.cpp new file mode 100644 index 0000000..676723d --- /dev/null +++ b/test/test_signed_random.cpp @@ -0,0 +1,184 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include +#include +#include +#include + +using namespace boost::safe_numbers; + +static std::mt19937_64 rng{42}; + +template +void test_default_construction() +{ + boost::random::uniform_int_distribution dist; + BOOST_TEST(dist.a() == (std::numeric_limits::min)()); + BOOST_TEST(dist.b() == (std::numeric_limits::max)()); + BOOST_TEST(dist.min() == (std::numeric_limits::min)()); + BOOST_TEST(dist.max() == (std::numeric_limits::max)()); +} + +template +void test_range_construction() +{ + auto lo = T{-5}; + auto hi = T{10}; + boost::random::uniform_int_distribution dist{lo, hi}; + BOOST_TEST(dist.a() == lo); + BOOST_TEST(dist.b() == hi); + BOOST_TEST(dist.min() == lo); + BOOST_TEST(dist.max() == hi); +} + +template +void test_generation() +{ + auto lo = T{-50}; + auto hi = T{50}; + boost::random::uniform_int_distribution dist{lo, hi}; + + for (int i {}; i < 1000; ++i) + { + auto val = dist(rng); + BOOST_TEST(val >= lo); + BOOST_TEST(val <= hi); + } +} + +template +void test_generation_with_param() +{ + using dist_t = boost::random::uniform_int_distribution; + using param_t = typename dist_t::param_type; + + dist_t dist; + auto lo = T{-25}; + auto hi = T{25}; + param_t p{lo, hi}; + + for (int i {}; i < 100; ++i) + { + auto val = dist(rng, p); + BOOST_TEST(val >= lo); + BOOST_TEST(val <= hi); + } +} + +template +void test_param_type() +{ + using dist_t = boost::random::uniform_int_distribution; + using param_t = typename dist_t::param_type; + + // Default construction + param_t p_default; + BOOST_TEST(p_default.a() == (std::numeric_limits::min)()); + BOOST_TEST(p_default.b() == (std::numeric_limits::max)()); + + // Range construction + auto lo = T{-10}; + auto hi = T{20}; + param_t p{lo, hi}; + BOOST_TEST(p.a() == lo); + BOOST_TEST(p.b() == hi); + + // Equality / inequality + param_t p2{lo, hi}; + BOOST_TEST(p == p2); + BOOST_TEST(!(p != p2)); + BOOST_TEST(p != p_default); + BOOST_TEST(!(p == p_default)); +} + +template +void test_param_construction() +{ + using dist_t = boost::random::uniform_int_distribution; + using param_t = typename dist_t::param_type; + + auto lo = T{-3}; + auto hi = T{30}; + param_t p{lo, hi}; + + dist_t dist{p}; + BOOST_TEST(dist.a() == lo); + BOOST_TEST(dist.b() == hi); +} + +template +void test_param_getter_setter() +{ + using dist_t = boost::random::uniform_int_distribution; + using param_t = typename dist_t::param_type; + + auto lo = T{-10}; + auto hi = T{10}; + dist_t dist{lo, hi}; + + // Getter + auto p = dist.param(); + BOOST_TEST(p.a() == lo); + BOOST_TEST(p.b() == hi); + + // Setter + auto lo2 = T{-50}; + auto hi2 = T{50}; + param_t p2{lo2, hi2}; + dist.param(p2); + BOOST_TEST(dist.a() == lo2); + BOOST_TEST(dist.b() == hi2); +} + +template +void test_reset() +{ + boost::random::uniform_int_distribution dist; + dist.reset(); + auto val = dist(rng); + BOOST_TEST(val >= (std::numeric_limits::min)()); + BOOST_TEST(val <= (std::numeric_limits::max)()); +} + +template +void test_equality() +{ + auto lo = T{-10}; + auto hi = T{10}; + boost::random::uniform_int_distribution dist1{lo, hi}; + boost::random::uniform_int_distribution dist2{lo, hi}; + boost::random::uniform_int_distribution dist3{lo, T{20}}; + + BOOST_TEST(dist1 == dist2); + BOOST_TEST(!(dist1 != dist2)); + BOOST_TEST(dist1 != dist3); + BOOST_TEST(!(dist1 == dist3)); +} + +template +void test_all() +{ + test_default_construction(); + test_range_construction(); + test_generation(); + test_generation_with_param(); + test_param_type(); + test_param_construction(); + test_param_getter_setter(); + test_reset(); + test_equality(); +} + +int main() +{ + test_all(); + test_all(); + test_all(); + test_all(); + test_all(); + + return boost::report_errors(); +} From 0b74187e032d89a58a790399251cdec44f945856 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 25 Mar 2026 12:31:40 -0400 Subject: [PATCH 10/16] Add streaming support to signed integers --- .../boost/safe_numbers/detail/type_traits.hpp | 6 +++ include/boost/safe_numbers/iostream.hpp | 41 ++++++++++++------- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/include/boost/safe_numbers/detail/type_traits.hpp b/include/boost/safe_numbers/detail/type_traits.hpp index db367d0..2b6a557 100644 --- a/include/boost/safe_numbers/detail/type_traits.hpp +++ b/include/boost/safe_numbers/detail/type_traits.hpp @@ -165,6 +165,9 @@ struct is_library_type : std::false_type {}; template struct is_library_type> : std::true_type {}; +template +struct is_library_type> : std::true_type {}; + template struct is_library_type> : std::true_type {}; @@ -174,6 +177,9 @@ struct is_integral_library_type : std::false_type {}; template struct is_integral_library_type> : std::true_type {}; +template +struct is_integral_library_type> : std::true_type {}; + template struct is_integral_library_type> : std::true_type {}; diff --git a/include/boost/safe_numbers/iostream.hpp b/include/boost/safe_numbers/iostream.hpp index 740d03a..7094180 100644 --- a/include/boost/safe_numbers/iostream.hpp +++ b/include/boost/safe_numbers/iostream.hpp @@ -29,24 +29,31 @@ auto operator>>(std::basic_istream& is, LibType& v) -> std::basic { using underlying_type = underlying_type_t; - if (is.peek() == static_cast('-')) + if constexpr (is_unsigned_library_type_v) { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, "Attempting to construct negative value with unsigned safe integer"); + if (is.peek() == static_cast('-')) + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, "Attempting to construct negative value with unsigned safe integer"); + } + } + + if constexpr (std::is_same_v) + { + std::uint32_t temp; + is >> temp; + v = static_cast(static_cast(temp)); + } + else if constexpr (std::is_same_v) + { + std::int32_t temp; + is >> temp; + v = static_cast(static_cast(temp)); } else { - if constexpr (std::is_same_v) - { - std::uint32_t temp; - is >> temp; - v = static_cast(static_cast(temp)); - } - else - { - underlying_type temp; - is >> temp; - v = static_cast(temp); - } + underlying_type temp; + is >> temp; + v = static_cast(temp); } return is; @@ -65,9 +72,13 @@ auto operator<<(std::basic_ostream& os, const LibType& v) -> std: if constexpr (std::is_same_v) { - // Display the value not the underlying representation like a carriage return + // Display the value, not the underlying representation like a carriage return os << static_cast(temp); } + else if constexpr (std::is_same_v) + { + os << static_cast(temp); + } else { os << temp; From b45e89ffaac237c6ccc2d63c978051fe49f668f9 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 25 Mar 2026 12:34:03 -0400 Subject: [PATCH 11/16] Update signed randomness testing with streaming --- test/test_signed_random.cpp | 59 +++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/test/test_signed_random.cpp b/test/test_signed_random.cpp index 676723d..1c16f8c 100644 --- a/test/test_signed_random.cpp +++ b/test/test_signed_random.cpp @@ -7,6 +7,7 @@ #include #include #include +#include using namespace boost::safe_numbers; @@ -158,6 +159,61 @@ void test_equality() BOOST_TEST(!(dist1 == dist3)); } +template +void test_dist_stream_roundtrip() +{ + auto lo = T{-3}; + auto hi = T{42}; + boost::random::uniform_int_distribution dist{lo, hi}; + + std::stringstream ss; + ss << dist; + + boost::random::uniform_int_distribution dist2; + ss >> dist2; + + BOOST_TEST(dist == dist2); + BOOST_TEST(dist2.a() == lo); + BOOST_TEST(dist2.b() == hi); +} + +template +void test_param_stream_roundtrip() +{ + using dist_t = boost::random::uniform_int_distribution; + using param_t = typename dist_t::param_type; + + auto lo = T{-7}; + auto hi = T{77}; + param_t p{lo, hi}; + + std::stringstream ss; + ss << p; + + param_t p2; + ss >> p2; + + BOOST_TEST(p == p2); + BOOST_TEST(p2.a() == lo); + BOOST_TEST(p2.b() == hi); +} + +template +void test_param_stream_invalid() +{ + using dist_t = boost::random::uniform_int_distribution; + using param_t = typename dist_t::param_type; + + // Write max < min (invalid) + std::stringstream ss; + ss << T{50} << " " << T{-5}; + + param_t p; + ss >> p; + + BOOST_TEST(ss.fail()); +} + template void test_all() { @@ -170,6 +226,9 @@ void test_all() test_param_getter_setter(); test_reset(); test_equality(); + test_dist_stream_roundtrip(); + test_param_stream_roundtrip(); + test_param_stream_invalid(); } int main() From 74f8e46b17de2ad574e2d54b0f5722a76ec8e99f Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 25 Mar 2026 12:49:35 -0400 Subject: [PATCH 12/16] Add random example and add to examples page --- doc/modules/ROOT/pages/examples.adoc | 23 ++++++++++++++++ examples/random.cpp | 39 ++++++++++++++++++++++++++++ test/Jamfile | 1 + 3 files changed, 63 insertions(+) create mode 100644 examples/random.cpp diff --git a/doc/modules/ROOT/pages/examples.adoc b/doc/modules/ROOT/pages/examples.adoc index e5bd60d..6996789 100644 --- a/doc/modules/ROOT/pages/examples.adoc +++ b/doc/modules/ROOT/pages/examples.adoc @@ -535,3 +535,26 @@ std::max(u16(100), u16(200)) = 200 std::clamp(u64(500), u64(0), u64(100)) = 100 ---- ==== + +[#examples_random] +== Random Number Generation + +The library integrates with https://www.boost.org/doc/libs/master/doc/html/boost_random.html[Boost.Random] by providing a `uniform_int_distribution` specialization for all safe integer types. +This supports the full distribution interface including `param_type`, stream I/O, and range-constrained generation. + +.This https://github.com/boostorg/safe_numbers/blob/develop/examples/random.cpp[example] demonstrates generating uniformly distributed safe integer values. +==== +[source, c++] +---- +include::example$random.cpp[] +---- + +Output: +---- +u32 in [1, 100]: 76 +i32 in [-50, 50]: 14 +u64 full range: 13874630024467741450 +u16 in [0, 10]: 1 +u128 in [0, 1000]: 904 +---- +==== diff --git a/examples/random.cpp b/examples/random.cpp new file mode 100644 index 0000000..3dd89cb --- /dev/null +++ b/examples/random.cpp @@ -0,0 +1,39 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include +#include +#include + +int main() +{ + using namespace boost::safe_numbers; + + std::mt19937_64 rng{42}; + + // Generate uniformly distributed unsigned values + boost::random::uniform_int_distribution u32_dist{u32{1}, u32{100}}; + std::cout << "u32 in [1, 100]: " << u32_dist(rng) << std::endl; + + // Generate uniformly distributed signed values + boost::random::uniform_int_distribution i32_dist{i32{-50}, i32{50}}; + std::cout << "i32 in [-50, 50]: " << i32_dist(rng) << std::endl; + + // Default range uses the full range of the type + boost::random::uniform_int_distribution u64_dist; + std::cout << "u64 full range: " << u64_dist(rng) << std::endl; + + // Use param_type to change range without reconstructing + using dist_t = boost::random::uniform_int_distribution; + dist_t dist; + dist_t::param_type narrow{u16{0}, u16{10}}; + std::cout << "u16 in [0, 10]: " << dist(rng, narrow) << std::endl; + + // 128-bit types work too + boost::random::uniform_int_distribution u128_dist{u128{0U}, u128{1000U}}; + std::cout << "u128 in [0, 1000]: " << u128_dist(rng) << std::endl; + + return 0; +} diff --git a/test/Jamfile b/test/Jamfile index 82dca58..0a25788 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -176,5 +176,6 @@ run ../examples/bitwise_ops.cpp ; run ../examples/safe_numerics_comparison.cpp ; run ../examples/byte_conversions.cpp ; run ../examples/std_numeric_usage.cpp ; +run ../examples/random.cpp ; run ../examples/basic_bounded_usage.cpp ; compile-fail ../examples/compile_fail_basic_bounded_usage_constexpr.cpp ; From 4e22857f1bd9d235edd0ad259a2af0a8d89ab1ab Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 25 Mar 2026 12:56:20 -0400 Subject: [PATCH 13/16] Add doc page on random value support --- doc/modules/ROOT/nav.adoc | 2 + doc/modules/ROOT/pages/random.adoc | 136 +++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 doc/modules/ROOT/pages/random.adoc diff --git a/doc/modules/ROOT/nav.adoc b/doc/modules/ROOT/nav.adoc index d063879..124f3e9 100644 --- a/doc/modules/ROOT/nav.adoc +++ b/doc/modules/ROOT/nav.adoc @@ -14,6 +14,7 @@ ** xref:examples.adoc#examples_fmt_format[Formatting] ** xref:examples.adoc#examples_iostream[Stream I/O] ** xref:examples.adoc#examples_bit[Bit Manipulation] +** xref:examples.adoc#examples_random[Random Number Generation] * xref:pretty_printers.adoc[] * xref:api_reference.adoc[] ** xref:api_reference.adoc#api_namespaces[Namespaces] @@ -33,5 +34,6 @@ * xref:integer_utilities.adoc[] * xref:numeric.adoc[] * xref:byte_conversions.adoc[] +* xref:random.adoc[] * xref:comparisons.adoc[] * xref:reference.adoc[] diff --git a/doc/modules/ROOT/pages/random.adoc b/doc/modules/ROOT/pages/random.adoc new file mode 100644 index 0000000..eb8487f --- /dev/null +++ b/doc/modules/ROOT/pages/random.adoc @@ -0,0 +1,136 @@ +//// +Copyright 2026 Matt Borland +Distributed under the Boost Software License, Version 1.0. +https://www.boost.org/LICENSE_1_0.txt +//// + +[#random] += Random Number Generation +:idprefix: random_ + +== Description + +The library provides a `boost::random::uniform_int_distribution` specialization for all safe integer types, enabling uniform random number generation that returns safe integers directly. +This works with any standard or Boost random engine (`std::mt19937`, `std::mt19937_64`, `boost::random::mt19937`, etc.). + +All unsigned types (`u8`, `u16`, `u32`, `u64`, `u128`), signed types (`i8`, `i16`, `i32`, `i64`, `i128`), and `bounded_uint` are supported. + +IMPORTANT: The header `` is *NOT* included in the convenience header ``, because it requires https://www.boost.org/doc/libs/master/doc/html/boost_random.html[Boost.Random] to be present. +You must include it explicitly. + +[source,c++] +---- +#include // Does NOT include random support +#include // Must be included separately +---- + +== Synopsis + +[source,c++] +---- +#include + +namespace boost::random { + +template + requires (safe_numbers::detail::is_unsigned_library_type_v || + safe_numbers::detail::is_signed_library_type_v) +class uniform_int_distribution +{ +public: + class param_type + { + public: + explicit param_type( + SafeT min = std::numeric_limits::min(), + SafeT max = std::numeric_limits::max()); + + auto a() const -> SafeT; + auto b() const -> SafeT; + + friend bool operator==(const param_type& lhs, const param_type& rhs); + friend bool operator!=(const param_type& lhs, const param_type& rhs); + + // Stream I/O + template + friend std::basic_ostream& + operator<<(std::basic_ostream& os, const param_type& p); + + template + friend std::basic_istream& + operator>>(std::basic_istream& is, param_type& p); + }; + + explicit uniform_int_distribution( + SafeT min = std::numeric_limits::min(), + SafeT max = std::numeric_limits::max()); + + explicit uniform_int_distribution(const param_type& param); + + // Generation + template + auto operator()(Engine& eng) const -> SafeT; + + template + auto operator()(Engine& eng, const param_type& param) const -> SafeT; + + // Observers + auto min() const -> SafeT; + auto max() const -> SafeT; + auto a() const -> SafeT; + auto b() const -> SafeT; + param_type param() const; + void param(const param_type& p); + void reset(); + + // Comparison + friend bool operator==(const uniform_int_distribution& lhs, + const uniform_int_distribution& rhs); + friend bool operator!=(const uniform_int_distribution& lhs, + const uniform_int_distribution& rhs); + + // Stream I/O + template + friend std::basic_ostream& + operator<<(std::basic_ostream& os, + const uniform_int_distribution& ud); + + template + friend std::basic_istream& + operator>>(std::basic_istream& is, + uniform_int_distribution& ud); +}; + +} // namespace boost::random +---- + +== Bounded Types + +For `bounded_uint`, the default range is the declared bounds of the type: + +[source,c++] +---- +using percent = bounded_uint; + +boost::random::uniform_int_distribution pct_dist; +auto pct = pct_dist(rng); // percent in [0, 100] +---- + +== Examples + +.This https://github.com/boostorg/safe_numbers/blob/develop/examples/random.cpp[example] demonstrates generating uniformly distributed safe integer values. +==== +[source, c++] +---- +include::example$random.cpp[] +---- + +Output: +---- +u32 in [1, 100]: 76 +i32 in [-50, 50]: 14 +u64 full range: 13874630024467741450 +u16 in [0, 10]: 1 +u128 in [0, 1000]: 904 +---- +==== From e99f38fdc1646cc9b9ba1e152d55fe927798171b Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 25 Mar 2026 12:58:49 -0400 Subject: [PATCH 14/16] Add testing of bounded types --- test/test_unsigned_random.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/test_unsigned_random.cpp b/test/test_unsigned_random.cpp index 07f215e..44e7c36 100644 --- a/test/test_unsigned_random.cpp +++ b/test/test_unsigned_random.cpp @@ -243,5 +243,9 @@ int main() test_all(); test_all(); + // Bounded types + test_all>(); + test_all>(); + return boost::report_errors(); } From a45e2c997610ffe7a1eccdc2d134b0f4b14e6135 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 25 Mar 2026 13:00:54 -0400 Subject: [PATCH 15/16] Fix default init of param type --- include/boost/safe_numbers/random.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/boost/safe_numbers/random.hpp b/include/boost/safe_numbers/random.hpp index c0eb693..807b712 100644 --- a/include/boost/safe_numbers/random.hpp +++ b/include/boost/safe_numbers/random.hpp @@ -63,8 +63,8 @@ class uniform_int_distribution BOOST_RANDOM_DETAIL_ISTREAM_OPERATOR(is, param_type, param) { - SafeT min_in; - SafeT max_in; + auto min_in = (std::numeric_limits::min)(); + auto max_in = (std::numeric_limits::min)(); if (is >> min_in >> std::ws >> max_in) { From 6feb32af8b3aa73d97132f06694cd79376e7a18f Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 25 Mar 2026 13:03:37 -0400 Subject: [PATCH 16/16] Ignore GCC and Clang warnings from boost.random --- include/boost/safe_numbers/random.hpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/include/boost/safe_numbers/random.hpp b/include/boost/safe_numbers/random.hpp index 807b712..fac6119 100644 --- a/include/boost/safe_numbers/random.hpp +++ b/include/boost/safe_numbers/random.hpp @@ -15,10 +15,24 @@ #ifndef BOOST_SAFE_NUMBERS_BUILD_MODULE +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#elif defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wold-style-cast" +#endif + #include #endif +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#elif defined(__clang__) +#pragma clang diagnostic pop +#endif + namespace boost::random { template