Skip to content

Commit fbbbcf6

Browse files
authored
Merge pull request #153 from cppalliance/140
2 parents 8944d12 + 6feb32a commit fbbbcf6

12 files changed

Lines changed: 909 additions & 15 deletions

File tree

doc/modules/ROOT/nav.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
** xref:examples.adoc#examples_fmt_format[Formatting]
1515
** xref:examples.adoc#examples_iostream[Stream I/O]
1616
** xref:examples.adoc#examples_bit[Bit Manipulation]
17+
** xref:examples.adoc#examples_random[Random Number Generation]
1718
* xref:pretty_printers.adoc[]
1819
* xref:api_reference.adoc[]
1920
** xref:api_reference.adoc#api_namespaces[Namespaces]
@@ -33,5 +34,6 @@
3334
* xref:integer_utilities.adoc[]
3435
* xref:numeric.adoc[]
3536
* xref:byte_conversions.adoc[]
37+
* xref:random.adoc[]
3638
* xref:comparisons.adoc[]
3739
* xref:reference.adoc[]

doc/modules/ROOT/pages/examples.adoc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,3 +535,26 @@ std::max(u16(100), u16(200)) = 200
535535
std::clamp(u64(500), u64(0), u64(100)) = 100
536536
----
537537
====
538+
539+
[#examples_random]
540+
== Random Number Generation
541+
542+
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.
543+
This supports the full distribution interface including `param_type`, stream I/O, and range-constrained generation.
544+
545+
.This https://github.com/boostorg/safe_numbers/blob/develop/examples/random.cpp[example] demonstrates generating uniformly distributed safe integer values.
546+
====
547+
[source, c++]
548+
----
549+
include::example$random.cpp[]
550+
----
551+
552+
Output:
553+
----
554+
u32 in [1, 100]: 76
555+
i32 in [-50, 50]: 14
556+
u64 full range: 13874630024467741450
557+
u16 in [0, 10]: 1
558+
u128 in [0, 1000]: 904
559+
----
560+
====

doc/modules/ROOT/pages/random.adoc

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
////
2+
Copyright 2026 Matt Borland
3+
Distributed under the Boost Software License, Version 1.0.
4+
https://www.boost.org/LICENSE_1_0.txt
5+
////
6+
7+
[#random]
8+
= Random Number Generation
9+
:idprefix: random_
10+
11+
== Description
12+
13+
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.
14+
This works with any standard or Boost random engine (`std::mt19937`, `std::mt19937_64`, `boost::random::mt19937`, etc.).
15+
16+
All unsigned types (`u8`, `u16`, `u32`, `u64`, `u128`), signed types (`i8`, `i16`, `i32`, `i64`, `i128`), and `bounded_uint<Min, Max>` are supported.
17+
18+
IMPORTANT: The header `<boost/safe_numbers/random.hpp>` is *NOT* included in the convenience header `<boost/safe_numbers.hpp>`, because it requires https://www.boost.org/doc/libs/master/doc/html/boost_random.html[Boost.Random] to be present.
19+
You must include it explicitly.
20+
21+
[source,c++]
22+
----
23+
#include <boost/safe_numbers.hpp> // Does NOT include random support
24+
#include <boost/safe_numbers/random.hpp> // Must be included separately
25+
----
26+
27+
== Synopsis
28+
29+
[source,c++]
30+
----
31+
#include <boost/safe_numbers/random.hpp>
32+
33+
namespace boost::random {
34+
35+
template <typename SafeT>
36+
requires (safe_numbers::detail::is_unsigned_library_type_v<SafeT> ||
37+
safe_numbers::detail::is_signed_library_type_v<SafeT>)
38+
class uniform_int_distribution<SafeT>
39+
{
40+
public:
41+
class param_type
42+
{
43+
public:
44+
explicit param_type(
45+
SafeT min = std::numeric_limits<SafeT>::min(),
46+
SafeT max = std::numeric_limits<SafeT>::max());
47+
48+
auto a() const -> SafeT;
49+
auto b() const -> SafeT;
50+
51+
friend bool operator==(const param_type& lhs, const param_type& rhs);
52+
friend bool operator!=(const param_type& lhs, const param_type& rhs);
53+
54+
// Stream I/O
55+
template <class CharT, class Traits>
56+
friend std::basic_ostream<CharT, Traits>&
57+
operator<<(std::basic_ostream<CharT, Traits>& os, const param_type& p);
58+
59+
template <class CharT, class Traits>
60+
friend std::basic_istream<CharT, Traits>&
61+
operator>>(std::basic_istream<CharT, Traits>& is, param_type& p);
62+
};
63+
64+
explicit uniform_int_distribution(
65+
SafeT min = std::numeric_limits<SafeT>::min(),
66+
SafeT max = std::numeric_limits<SafeT>::max());
67+
68+
explicit uniform_int_distribution(const param_type& param);
69+
70+
// Generation
71+
template <typename Engine>
72+
auto operator()(Engine& eng) const -> SafeT;
73+
74+
template <typename Engine>
75+
auto operator()(Engine& eng, const param_type& param) const -> SafeT;
76+
77+
// Observers
78+
auto min() const -> SafeT;
79+
auto max() const -> SafeT;
80+
auto a() const -> SafeT;
81+
auto b() const -> SafeT;
82+
param_type param() const;
83+
void param(const param_type& p);
84+
void reset();
85+
86+
// Comparison
87+
friend bool operator==(const uniform_int_distribution& lhs,
88+
const uniform_int_distribution& rhs);
89+
friend bool operator!=(const uniform_int_distribution& lhs,
90+
const uniform_int_distribution& rhs);
91+
92+
// Stream I/O
93+
template <class CharT, class Traits>
94+
friend std::basic_ostream<CharT, Traits>&
95+
operator<<(std::basic_ostream<CharT, Traits>& os,
96+
const uniform_int_distribution& ud);
97+
98+
template <class CharT, class Traits>
99+
friend std::basic_istream<CharT, Traits>&
100+
operator>>(std::basic_istream<CharT, Traits>& is,
101+
uniform_int_distribution& ud);
102+
};
103+
104+
} // namespace boost::random
105+
----
106+
107+
== Bounded Types
108+
109+
For `bounded_uint<Min, Max>`, the default range is the declared bounds of the type:
110+
111+
[source,c++]
112+
----
113+
using percent = bounded_uint<u8{0}, u8{100}>;
114+
115+
boost::random::uniform_int_distribution<percent> pct_dist;
116+
auto pct = pct_dist(rng); // percent in [0, 100]
117+
----
118+
119+
== Examples
120+
121+
.This https://github.com/boostorg/safe_numbers/blob/develop/examples/random.cpp[example] demonstrates generating uniformly distributed safe integer values.
122+
====
123+
[source, c++]
124+
----
125+
include::example$random.cpp[]
126+
----
127+
128+
Output:
129+
----
130+
u32 in [1, 100]: 76
131+
i32 in [-50, 50]: 14
132+
u64 full range: 13874630024467741450
133+
u16 in [0, 10]: 1
134+
u128 in [0, 1000]: 904
135+
----
136+
====

doc/modules/ROOT/pages/reference.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ The following books: papers and blog posts serve as the basis for the algorithms
2121
7. S. Glesner, “Finite Integer Computations: An Algebraic Foundation for Their Correctness,” Formal Aspects of Computing, vol. 18, no. 2, pp. 244-262, 2006.
2222
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.
2323
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.
24+
10. L. Valenty, "P2993 Constrained Numbers," https://wg21.link/p2993

examples/random.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright 2026 Matt Borland
2+
// Distributed under the Boost Software License, Version 1.0.
3+
// https://www.boost.org/LICENSE_1_0.txt
4+
5+
#include <boost/safe_numbers.hpp>
6+
#include <boost/safe_numbers/random.hpp>
7+
#include <iostream>
8+
#include <random>
9+
10+
int main()
11+
{
12+
using namespace boost::safe_numbers;
13+
14+
std::mt19937_64 rng{42};
15+
16+
// Generate uniformly distributed unsigned values
17+
boost::random::uniform_int_distribution<u32> u32_dist{u32{1}, u32{100}};
18+
std::cout << "u32 in [1, 100]: " << u32_dist(rng) << std::endl;
19+
20+
// Generate uniformly distributed signed values
21+
boost::random::uniform_int_distribution<i32> i32_dist{i32{-50}, i32{50}};
22+
std::cout << "i32 in [-50, 50]: " << i32_dist(rng) << std::endl;
23+
24+
// Default range uses the full range of the type
25+
boost::random::uniform_int_distribution<u64> u64_dist;
26+
std::cout << "u64 full range: " << u64_dist(rng) << std::endl;
27+
28+
// Use param_type to change range without reconstructing
29+
using dist_t = boost::random::uniform_int_distribution<u16>;
30+
dist_t dist;
31+
dist_t::param_type narrow{u16{0}, u16{10}};
32+
std::cout << "u16 in [0, 10]: " << dist(rng, narrow) << std::endl;
33+
34+
// 128-bit types work too
35+
boost::random::uniform_int_distribution<u128> u128_dist{u128{0U}, u128{1000U}};
36+
std::cout << "u128 in [0, 1000]: " << u128_dist(rng) << std::endl;
37+
38+
return 0;
39+
}

include/boost/safe_numbers.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
#ifndef BOOST_SAFENUMBERS_HPP
66
#define BOOST_SAFENUMBERS_HPP
77

8+
#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_CONVERSION
9+
810
#include <boost/safe_numbers/unsigned_integers.hpp>
911
#include <boost/safe_numbers/signed_integers.hpp>
1012
#include <boost/safe_numbers/bounded_integers.hpp>
@@ -18,4 +20,6 @@
1820
#include <boost/safe_numbers/byte_conversions.hpp>
1921
#include <boost/safe_numbers/numeric.hpp>
2022

23+
#undef BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_CONVERSION
24+
2125
#endif //BOOST_SAFENUMBERS_HPP

include/boost/safe_numbers/detail/type_traits.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,9 @@ struct is_library_type : std::false_type {};
165165
template <typename T>
166166
struct is_library_type<unsigned_integer_basis<T>> : std::true_type {};
167167

168+
template <typename T>
169+
struct is_library_type<signed_integer_basis<T>> : std::true_type {};
170+
168171
template <auto Min, auto Max>
169172
struct is_library_type<bounded_uint<Min, Max>> : std::true_type {};
170173

@@ -174,6 +177,9 @@ struct is_integral_library_type : std::false_type {};
174177
template <typename T>
175178
struct is_integral_library_type<unsigned_integer_basis<T>> : std::true_type {};
176179

180+
template <typename T>
181+
struct is_integral_library_type<signed_integer_basis<T>> : std::true_type {};
182+
177183
template <auto Min, auto Max>
178184
struct is_integral_library_type<bounded_uint<Min, Max>> : std::true_type {};
179185

include/boost/safe_numbers/iostream.hpp

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,24 +29,31 @@ auto operator>>(std::basic_istream<charT, traits>& is, LibType& v) -> std::basic
2929
{
3030
using underlying_type = underlying_type_t<LibType>;
3131

32-
if (is.peek() == static_cast<charT>('-'))
32+
if constexpr (is_unsigned_library_type_v<LibType>)
3333
{
34-
BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, "Attempting to construct negative value with unsigned safe integer");
34+
if (is.peek() == static_cast<charT>('-'))
35+
{
36+
BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, "Attempting to construct negative value with unsigned safe integer");
37+
}
38+
}
39+
40+
if constexpr (std::is_same_v<underlying_type, std::uint8_t>)
41+
{
42+
std::uint32_t temp;
43+
is >> temp;
44+
v = static_cast<LibType>(static_cast<std::uint8_t>(temp));
45+
}
46+
else if constexpr (std::is_same_v<underlying_type, std::int8_t>)
47+
{
48+
std::int32_t temp;
49+
is >> temp;
50+
v = static_cast<LibType>(static_cast<std::int8_t>(temp));
3551
}
3652
else
3753
{
38-
if constexpr (std::is_same_v<underlying_type, std::uint8_t>)
39-
{
40-
std::uint32_t temp;
41-
is >> temp;
42-
v = static_cast<LibType>(static_cast<std::uint8_t>(temp));
43-
}
44-
else
45-
{
46-
underlying_type temp;
47-
is >> temp;
48-
v = static_cast<LibType>(temp);
49-
}
54+
underlying_type temp;
55+
is >> temp;
56+
v = static_cast<LibType>(temp);
5057
}
5158

5259
return is;
@@ -65,9 +72,13 @@ auto operator<<(std::basic_ostream<charT, traits>& os, const LibType& v) -> std:
6572

6673
if constexpr (std::is_same_v<underlying_type, std::uint8_t>)
6774
{
68-
// Display the value not the underlying representation like a carriage return
75+
// Display the value, not the underlying representation like a carriage return
6976
os << static_cast<std::uint32_t>(temp);
7077
}
78+
else if constexpr (std::is_same_v<underlying_type, std::int8_t>)
79+
{
80+
os << static_cast<std::int32_t>(temp);
81+
}
7182
else
7283
{
7384
os << temp;

0 commit comments

Comments
 (0)