Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -33,5 +34,6 @@
* xref:integer_utilities.adoc[]
* xref:numeric.adoc[]
* xref:byte_conversions.adoc[]
* xref:random.adoc[]
* xref:comparisons.adoc[]
* xref:reference.adoc[]
23 changes: 23 additions & 0 deletions doc/modules/ROOT/pages/examples.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
----
====
136 changes: 136 additions & 0 deletions doc/modules/ROOT/pages/random.adoc
Original file line number Diff line number Diff line change
@@ -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<Min, Max>` are supported.

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.
You must include it explicitly.

[source,c++]
----
#include <boost/safe_numbers.hpp> // Does NOT include random support
#include <boost/safe_numbers/random.hpp> // Must be included separately
----

== Synopsis

[source,c++]
----
#include <boost/safe_numbers/random.hpp>

namespace boost::random {

template <typename SafeT>
requires (safe_numbers::detail::is_unsigned_library_type_v<SafeT> ||
safe_numbers::detail::is_signed_library_type_v<SafeT>)
class uniform_int_distribution<SafeT>
{
public:
class param_type
{
public:
explicit param_type(
SafeT min = std::numeric_limits<SafeT>::min(),
SafeT max = std::numeric_limits<SafeT>::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 <class CharT, class Traits>
friend std::basic_ostream<CharT, Traits>&
operator<<(std::basic_ostream<CharT, Traits>& os, const param_type& p);

template <class CharT, class Traits>
friend std::basic_istream<CharT, Traits>&
operator>>(std::basic_istream<CharT, Traits>& is, param_type& p);
};

explicit uniform_int_distribution(
SafeT min = std::numeric_limits<SafeT>::min(),
SafeT max = std::numeric_limits<SafeT>::max());

explicit uniform_int_distribution(const param_type& param);

// Generation
template <typename Engine>
auto operator()(Engine& eng) const -> SafeT;

template <typename Engine>
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 <class CharT, class Traits>
friend std::basic_ostream<CharT, Traits>&
operator<<(std::basic_ostream<CharT, Traits>& os,
const uniform_int_distribution& ud);

template <class CharT, class Traits>
friend std::basic_istream<CharT, Traits>&
operator>>(std::basic_istream<CharT, Traits>& is,
uniform_int_distribution& ud);
};

} // namespace boost::random
----

== Bounded Types

For `bounded_uint<Min, Max>`, the default range is the declared bounds of the type:

[source,c++]
----
using percent = bounded_uint<u8{0}, u8{100}>;

boost::random::uniform_int_distribution<percent> 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
----
====
1 change: 1 addition & 0 deletions doc/modules/ROOT/pages/reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
39 changes: 39 additions & 0 deletions examples/random.cpp
Original file line number Diff line number Diff line change
@@ -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 <boost/safe_numbers.hpp>
#include <boost/safe_numbers/random.hpp>
#include <iostream>
#include <random>

int main()
{
using namespace boost::safe_numbers;

std::mt19937_64 rng{42};

// Generate uniformly distributed unsigned values
boost::random::uniform_int_distribution<u32> 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> 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> 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<u16>;
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> u128_dist{u128{0U}, u128{1000U}};
std::cout << "u128 in [0, 1000]: " << u128_dist(rng) << std::endl;

return 0;
}
4 changes: 4 additions & 0 deletions include/boost/safe_numbers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#ifndef BOOST_SAFENUMBERS_HPP
#define BOOST_SAFENUMBERS_HPP

#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_CONVERSION

#include <boost/safe_numbers/unsigned_integers.hpp>
#include <boost/safe_numbers/signed_integers.hpp>
#include <boost/safe_numbers/bounded_integers.hpp>
Expand All @@ -18,4 +20,6 @@
#include <boost/safe_numbers/byte_conversions.hpp>
#include <boost/safe_numbers/numeric.hpp>

#undef BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_CONVERSION

#endif //BOOST_SAFENUMBERS_HPP
6 changes: 6 additions & 0 deletions include/boost/safe_numbers/detail/type_traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ struct is_library_type : std::false_type {};
template <typename T>
struct is_library_type<unsigned_integer_basis<T>> : std::true_type {};

template <typename T>
struct is_library_type<signed_integer_basis<T>> : std::true_type {};

template <auto Min, auto Max>
struct is_library_type<bounded_uint<Min, Max>> : std::true_type {};

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

template <typename T>
struct is_integral_library_type<signed_integer_basis<T>> : std::true_type {};

template <auto Min, auto Max>
struct is_integral_library_type<bounded_uint<Min, Max>> : std::true_type {};

Expand Down
41 changes: 26 additions & 15 deletions include/boost/safe_numbers/iostream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,31 @@ auto operator>>(std::basic_istream<charT, traits>& is, LibType& v) -> std::basic
{
using underlying_type = underlying_type_t<LibType>;

if (is.peek() == static_cast<charT>('-'))
if constexpr (is_unsigned_library_type_v<LibType>)
{
BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, "Attempting to construct negative value with unsigned safe integer");
if (is.peek() == static_cast<charT>('-'))
{
BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, "Attempting to construct negative value with unsigned safe integer");
}
}

if constexpr (std::is_same_v<underlying_type, std::uint8_t>)
{
std::uint32_t temp;
is >> temp;
v = static_cast<LibType>(static_cast<std::uint8_t>(temp));
}
else if constexpr (std::is_same_v<underlying_type, std::int8_t>)
{
std::int32_t temp;
is >> temp;
v = static_cast<LibType>(static_cast<std::int8_t>(temp));
}
else
{
if constexpr (std::is_same_v<underlying_type, std::uint8_t>)
{
std::uint32_t temp;
is >> temp;
v = static_cast<LibType>(static_cast<std::uint8_t>(temp));
}
else
{
underlying_type temp;
is >> temp;
v = static_cast<LibType>(temp);
}
underlying_type temp;
is >> temp;
v = static_cast<LibType>(temp);
}

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

if constexpr (std::is_same_v<underlying_type, std::uint8_t>)
{
// 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<std::uint32_t>(temp);
}
else if constexpr (std::is_same_v<underlying_type, std::int8_t>)
{
os << static_cast<std::int32_t>(temp);
}
else
{
os << temp;
Expand Down
Loading
Loading