From 541aaea8c6786d5b97a31eca3f2dc1fdb8a38104 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 3 Feb 2026 15:43:58 -0500 Subject: [PATCH 1/7] Add checked policy and impl --- .../safe_numbers/detail/overflow_policy.hpp | 1 + .../detail/unsigned_integer_basis.hpp | 199 +++++++++++++++++- 2 files changed, 197 insertions(+), 3 deletions(-) diff --git a/include/boost/safe_numbers/detail/overflow_policy.hpp b/include/boost/safe_numbers/detail/overflow_policy.hpp index 185127c..614d7c6 100644 --- a/include/boost/safe_numbers/detail/overflow_policy.hpp +++ b/include/boost/safe_numbers/detail/overflow_policy.hpp @@ -12,6 +12,7 @@ enum class overflow_policy throw_exception, saturate, overflow_tuple, + checked, }; } // namespace boost::safe_numbers::detail diff --git a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp index eab8458..c055814 100644 --- a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp +++ b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp @@ -18,6 +18,7 @@ #include #include #include +#include #endif // BOOST_SAFE_NUMBERS_BUILD_MODULE @@ -268,10 +269,42 @@ struct add_helper } }; +// Partial specialization for overflow_tuple policy +template +struct add_helper +{ + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> std::optional> + { + using result_type = unsigned_integer_basis; + + const auto lhs_basis {static_cast(lhs)}; + const auto rhs_basis {static_cast(rhs)}; + BasisType res {}; + + if constexpr (!std::is_same_v) + { + #if BOOST_SAFE_NUMBERS_HAS_BUILTIN(__builtin_add_overflow) || BOOST_SAFE_NUMBERS_HAS_BUILTIN(_addcarry_u64) || defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X86_INTRIN) + + if (!std::is_constant_evaluated()) + { + const auto overflowed {impl::unsigned_intrin_add(lhs_basis, rhs_basis, res)}; + return overflowed ? std::nullopt : std::make_optional(result_type{res}); + } + + #endif + } + + const auto overflowed {impl::unsigned_no_intrin_add(lhs_basis, rhs_basis, res)}; + return overflowed ? std::nullopt : std::make_optional(result_type{res}); + } +}; + template [[nodiscard]] constexpr auto add_impl(const unsigned_integer_basis lhs, const unsigned_integer_basis rhs) - noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple) + noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || Policy == overflow_policy::checked) { return add_helper::apply(lhs, rhs); } @@ -567,10 +600,42 @@ struct sub_helper } }; +// Partial specialization for checked policy +template +struct sub_helper +{ + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> std::optional> + { + using result_type = unsigned_integer_basis; + + const auto lhs_basis {static_cast(lhs)}; + const auto rhs_basis {static_cast(rhs)}; + BasisType res {}; + + if constexpr (!std::is_same_v) + { + #if BOOST_SAFE_NUMBERS_HAS_BUILTIN(__builtin_sub_overflow) || BOOST_SAFE_NUMBERS_HAS_BUILTIN(_subborrow_u64) || defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X86_INTRIN) + + if (!std::is_constant_evaluated()) + { + const auto underflowed {impl::unsigned_intrin_sub(lhs_basis, rhs_basis, res)}; + return underflowed ? std::nullopt : std::make_optional(result_type{res}); + } + + #endif + } + + const auto underflowed {impl::unsigned_no_intrin_sub(lhs_basis, rhs_basis, res)}; + return underflowed ? std::nullopt : std::make_optional(result_type{res}); + } +}; + template [[nodiscard]] constexpr auto sub_impl(const unsigned_integer_basis lhs, const unsigned_integer_basis rhs) - noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple) + noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || Policy == overflow_policy::checked) { return sub_helper::apply(lhs, rhs); } @@ -767,10 +832,42 @@ struct mul_helper } }; +// Partial specialization for checked policy +template +struct mul_helper +{ + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> std::optional> + { + using result_type = unsigned_integer_basis; + + const auto lhs_basis {static_cast(lhs)}; + const auto rhs_basis {static_cast(rhs)}; + BasisType res {}; + + if constexpr (!std::is_same_v) + { + #if BOOST_SAFE_NUMBERS_HAS_BUILTIN(__builtin_mul_overflow) || BOOST_SAFE_NUMBERS_HAS_BUILTIN(_umul128) + + if (!std::is_constant_evaluated()) + { + const auto overflowed {impl::unsigned_intrin_mul(lhs_basis, rhs_basis, res)}; + return overflowed ? std::nullopt : std::make_optional(result_type{res}); + } + + #endif + } + + const auto overflowed {impl::no_intrin_mul(lhs_basis, rhs_basis, res)}; + return overflowed ? std::nullopt : std::make_optional(result_type{res}); + } +}; + template [[nodiscard]] constexpr auto mul_impl(const unsigned_integer_basis lhs, const unsigned_integer_basis rhs) - noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple) + noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || Policy == overflow_policy::checked) { return mul_helper::apply(lhs, rhs); } @@ -861,9 +958,37 @@ struct div_helper } }; +// Partial specialization for checked policy +template +struct div_helper +{ + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> std::optional> + { + using result_type = unsigned_integer_basis; + + const auto divisor {static_cast(rhs)}; + if (divisor == 0U) [[unlikely]] + { + return std::nullopt; + } + + if constexpr (std::is_same_v || std::is_same_v) + { + return std::make_optional(result_type{static_cast(static_cast(lhs) / divisor)}); + } + else + { + return std::make_optional(result_type{static_cast(lhs) / divisor}); + } + } +}; + template [[nodiscard]] constexpr auto div_impl(const unsigned_integer_basis lhs, const unsigned_integer_basis rhs) + noexcept(Policy == overflow_policy::checked) { return div_helper::apply(lhs, rhs); } @@ -954,9 +1079,37 @@ struct mod_helper } }; +// Partial specialization for checked policy +template +struct mod_helper +{ + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> std::optional> + { + using result_type = unsigned_integer_basis; + + const auto divisor {static_cast(rhs)}; + if (divisor == 0U) [[unlikely]] + { + return std::nullopt; + } + + if constexpr (std::is_same_v || std::is_same_v) + { + return std::make_optional(result_type{static_cast(static_cast(lhs) % divisor)}); + } + else + { + return std::make_optional(result_type{static_cast(lhs) % divisor}); + } + } +}; + template [[nodiscard]] constexpr auto mod_impl(const unsigned_integer_basis lhs, const unsigned_integer_basis rhs) + noexcept(Policy == overflow_policy::checked) { return mod_helper::apply(lhs, rhs); } @@ -1139,6 +1292,46 @@ template return detail::mod_impl(lhs, rhs); } +template +[[nodiscard]] constexpr auto checked_add(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::optional> +{ + return detail::add_impl(lhs, rhs); +} + +template +[[nodiscard]] constexpr auto checked_sub(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::optional> +{ + return detail::sub_impl(lhs, rhs); +} + +template +[[nodiscard]] constexpr auto checked_mul(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::optional> +{ + return detail::mul_impl(lhs, rhs); +} + +template +[[nodiscard]] constexpr auto checked_div(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::optional> +{ + return detail::div_impl(lhs, rhs); +} + +template +[[nodiscard]] constexpr auto checked_mod(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::optional> +{ + return detail::mod_impl(lhs, rhs); +} + } // namespace boost::safe_numbers #undef BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP From b6d490576ee7a188be2141c8f8b6a368babce3b9 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 3 Feb 2026 15:58:19 -0500 Subject: [PATCH 2/7] Remove outdated doc note --- doc/modules/ROOT/pages/charconv.adoc | 2 -- 1 file changed, 2 deletions(-) diff --git a/doc/modules/ROOT/pages/charconv.adoc b/doc/modules/ROOT/pages/charconv.adoc index 3638ef0..b019da0 100644 --- a/doc/modules/ROOT/pages/charconv.adoc +++ b/doc/modules/ROOT/pages/charconv.adoc @@ -10,8 +10,6 @@ https://www.boost.org/LICENSE_1_0.txt The following functions provide character conversion support for safe integer types using https://www.boost.org/doc/libs/master/libs/charconv/doc/html/charconv.html[Boost.Charconv]. -IMPORTANT: Using this header requires Boost.Charconv headers to be present. - [#to_chars] == to_chars From f2d4497db06dbbde6499d3950791e706d4e3ef24 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 3 Feb 2026 15:58:34 -0500 Subject: [PATCH 3/7] Add mixed type functions --- .../detail/unsigned_integer_basis.hpp | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp index c055814..0609452 100644 --- a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp +++ b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp @@ -1260,6 +1260,8 @@ template return detail::add_impl(lhs, rhs); } +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("overflowing addition", overflowing_add) + template [[nodiscard]] constexpr auto overflowing_sub(const detail::unsigned_integer_basis lhs, const detail::unsigned_integer_basis rhs) noexcept @@ -1268,6 +1270,8 @@ template return detail::sub_impl(lhs, rhs); } +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("overflowing subtraction", overflowing_sub) + template [[nodiscard]] constexpr auto overflowing_mul(const detail::unsigned_integer_basis lhs, const detail::unsigned_integer_basis rhs) noexcept @@ -1276,6 +1280,8 @@ template return detail::mul_impl(lhs, rhs); } +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("overflowing multiplication", overflowing_mul) + template [[nodiscard]] constexpr auto overflowing_div(const detail::unsigned_integer_basis lhs, const detail::unsigned_integer_basis rhs) @@ -1284,6 +1290,8 @@ template return detail::div_impl(lhs, rhs); } +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("overflowing division", overflowing_div) + template [[nodiscard]] constexpr auto overflowing_mod(const detail::unsigned_integer_basis lhs, const detail::unsigned_integer_basis rhs) @@ -1292,6 +1300,8 @@ template return detail::mod_impl(lhs, rhs); } +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("overflowing modulo", overflowing_mod) + template [[nodiscard]] constexpr auto checked_add(const detail::unsigned_integer_basis lhs, const detail::unsigned_integer_basis rhs) noexcept @@ -1300,6 +1310,8 @@ template return detail::add_impl(lhs, rhs); } +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("checked addition", checked_add) + template [[nodiscard]] constexpr auto checked_sub(const detail::unsigned_integer_basis lhs, const detail::unsigned_integer_basis rhs) noexcept @@ -1308,6 +1320,8 @@ template return detail::sub_impl(lhs, rhs); } +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("checked subtraction", checked_sub) + template [[nodiscard]] constexpr auto checked_mul(const detail::unsigned_integer_basis lhs, const detail::unsigned_integer_basis rhs) noexcept @@ -1316,6 +1330,8 @@ template return detail::mul_impl(lhs, rhs); } +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("checked multiplication", checked_mul) + template [[nodiscard]] constexpr auto checked_div(const detail::unsigned_integer_basis lhs, const detail::unsigned_integer_basis rhs) noexcept @@ -1324,6 +1340,8 @@ template return detail::div_impl(lhs, rhs); } +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("checked division", checked_div) + template [[nodiscard]] constexpr auto checked_mod(const detail::unsigned_integer_basis lhs, const detail::unsigned_integer_basis rhs) noexcept @@ -1332,6 +1350,8 @@ template return detail::mod_impl(lhs, rhs); } +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("checked modulo", checked_mod) + } // namespace boost::safe_numbers #undef BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP From 433bb6f8b1827b85d12091b3c27ddb137192b700 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 3 Feb 2026 16:03:39 -0500 Subject: [PATCH 4/7] Add checked add and sub testing --- test/Jamfile | 2 + test/test_unsigned_checked_addition.cpp | 152 ++++++++++++++++++++ test/test_unsigned_checked_subtraction.cpp | 156 +++++++++++++++++++++ 3 files changed, 310 insertions(+) create mode 100644 test/test_unsigned_checked_addition.cpp create mode 100644 test/test_unsigned_checked_subtraction.cpp diff --git a/test/Jamfile b/test/Jamfile index da32648..0a3ba18 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -80,6 +80,8 @@ run test_unsigned_overflowing_subtraction.cpp ; run test_unsigned_overflowing_multiplication.cpp ; run test_unsigned_overflowing_division.cpp ; run test_unsigned_overflowing_mod.cpp ; +run test_unsigned_checked_addition.cpp ; +run test_unsigned_checked_subtraction.cpp ; # Compile Tests compile compile_tests/compile_test_unsigned_integers.cpp ; diff --git a/test/test_unsigned_checked_addition.cpp b/test/test_unsigned_checked_addition.cpp new file mode 100644 index 0000000..7a28c5f --- /dev/null +++ b/test/test_unsigned_checked_addition.cpp @@ -0,0 +1,152 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wold-style-cast" +# pragma clang diagnostic ignored "-Wundef" +# pragma clang diagnostic ignored "-Wconversion" +# pragma clang diagnostic ignored "-Wsign-conversion" +# pragma clang diagnostic ignored "-Wfloat-equal" +# pragma clang diagnostic ignored "-Wsign-compare" +# pragma clang diagnostic ignored "-Woverflow" + +# if (__clang_major__ >= 10 && !defined(__APPLE__)) || __clang_major__ >= 13 +# pragma clang diagnostic ignored "-Wdeprecated-copy" +# endif + +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wold-style-cast" +# pragma GCC diagnostic ignored "-Wundef" +# pragma GCC diagnostic ignored "-Wconversion" +# pragma GCC diagnostic ignored "-Wsign-conversion" +# pragma GCC diagnostic ignored "-Wsign-compare" +# pragma GCC diagnostic ignored "-Wfloat-equal" +# pragma GCC diagnostic ignored "-Woverflow" + +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4389) +# pragma warning(disable : 4127) +# pragma warning(disable : 4305) +# pragma warning(disable : 4309) +#endif + +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_COMPARE +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_CONVERSION + +#include + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +// Ignore [[nodiscard]] on the test that we know are going to throw +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-result" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-result" +#elif defined(_MSC_VER) +# pragma warning (push) +# pragma warning (disable: 4834) +#endif + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include +#include +#include +#include + +#endif + +using namespace boost::safe_numbers; + +inline std::mt19937_64 rng{42}; +inline constexpr std::size_t N {1024}; + +template +void test_valid_addition() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {std::numeric_limits::min() / 2U, + std::numeric_limits::max() / 2U}; + + for (std::size_t i = 0; i < N; ++i) + { + const auto lhs_value {dist(rng)}; + const auto rhs_value {dist(rng)}; + + T ref_value {}; + if constexpr (std::is_same_v || std::is_same_v) + { + ref_value = static_cast(static_cast(static_cast(lhs_value + rhs_value))); + } + else + { + ref_value = static_cast(lhs_value + rhs_value); + } + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto res {checked_add(lhs, rhs)}; + + BOOST_TEST(res.has_value()); + BOOST_TEST_EQ(ref_value, res.value()); + } +} + +template +void test_checked_addition() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {2U, std::numeric_limits::max()}; + + for (std::size_t i = 0; i < N; ++i) + { + constexpr basis_type lhs_value {std::numeric_limits::max() - 1U}; + const auto rhs_value {dist(rng)}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto res {checked_add(lhs, rhs)}; + + BOOST_TEST(!res.has_value()); + } +} + +int main() +{ + test_valid_addition(); + test_checked_addition(); + + test_valid_addition(); + test_checked_addition(); + + test_valid_addition(); + test_checked_addition(); + + test_valid_addition(); + test_checked_addition(); + + test_valid_addition(); + test_checked_addition(); + + return boost::report_errors(); +} diff --git a/test/test_unsigned_checked_subtraction.cpp b/test/test_unsigned_checked_subtraction.cpp new file mode 100644 index 0000000..0986f0c --- /dev/null +++ b/test/test_unsigned_checked_subtraction.cpp @@ -0,0 +1,156 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wold-style-cast" +# pragma clang diagnostic ignored "-Wundef" +# pragma clang diagnostic ignored "-Wconversion" +# pragma clang diagnostic ignored "-Wsign-conversion" +# pragma clang diagnostic ignored "-Wfloat-equal" +# pragma clang diagnostic ignored "-Wsign-compare" +# pragma clang diagnostic ignored "-Woverflow" + +# if (__clang_major__ >= 10 && !defined(__APPLE__)) || __clang_major__ >= 13 +# pragma clang diagnostic ignored "-Wdeprecated-copy" +# endif + +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wold-style-cast" +# pragma GCC diagnostic ignored "-Wundef" +# pragma GCC diagnostic ignored "-Wconversion" +# pragma GCC diagnostic ignored "-Wsign-conversion" +# pragma GCC diagnostic ignored "-Wsign-compare" +# pragma GCC diagnostic ignored "-Wfloat-equal" +# pragma GCC diagnostic ignored "-Woverflow" + +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4389) +# pragma warning(disable : 4127) +# pragma warning(disable : 4305) +# pragma warning(disable : 4309) +#endif + +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_COMPARE +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_CONVERSION + +#include + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +// Ignore [[nodiscard]] on the test that we know are going to throw +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-result" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-result" +#elif defined(_MSC_VER) +# pragma warning (push) +# pragma warning (disable: 4834) +#endif + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include +#include +#include +#include +#include + +#endif + +using namespace boost::safe_numbers; + +inline std::mt19937_64 rng{42}; +inline constexpr std::size_t N {1024}; + +template +void test_valid_subtraction() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {std::numeric_limits::min() / 2U, + std::numeric_limits::max() / 2U}; + + for (std::size_t i = 0; i < N; ++i) + { + auto lhs_value {dist(rng)}; + auto rhs_value {dist(rng)}; + + if (lhs_value < rhs_value) + { + std::swap(lhs_value, rhs_value); + } + + T ref_value {}; + if constexpr (std::is_same_v || std::is_same_v) + { + ref_value = static_cast(static_cast(static_cast(lhs_value - rhs_value))); + } + else + { + ref_value = static_cast(lhs_value - rhs_value); + } + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto res {checked_sub(lhs, rhs)}; + + BOOST_TEST(res.has_value()); + BOOST_TEST(ref_value == res); + } +} + +template +void test_checked_subtraction() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {1U, std::numeric_limits::max() - 1U}; + + for (std::size_t i = 0; i < N; ++i) + { + const auto lhs_value {dist(rng)}; + constexpr basis_type rhs_value {std::numeric_limits::max()}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto res {checked_sub(lhs, rhs)}; + + BOOST_TEST(!res.has_value()); + } +} + + +int main() +{ + test_valid_subtraction(); + test_valid_subtraction(); + test_valid_subtraction(); + test_valid_subtraction(); + test_valid_subtraction(); + + test_checked_subtraction(); + test_checked_subtraction(); + test_checked_subtraction(); + test_checked_subtraction(); + test_checked_subtraction(); + + return boost::report_errors(); +} From 9b31939acd17f44c3943afb194c4c859c1efe309 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 3 Feb 2026 16:14:52 -0500 Subject: [PATCH 5/7] Add checked mul testing --- test/Jamfile | 1 + test/test_unsigned_checked_multiplication.cpp | 152 ++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 test/test_unsigned_checked_multiplication.cpp diff --git a/test/Jamfile b/test/Jamfile index 0a3ba18..4b39399 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -82,6 +82,7 @@ run test_unsigned_overflowing_division.cpp ; run test_unsigned_overflowing_mod.cpp ; run test_unsigned_checked_addition.cpp ; run test_unsigned_checked_subtraction.cpp ; +run test_unsigned_checked_multiplication.cpp ; # Compile Tests compile compile_tests/compile_test_unsigned_integers.cpp ; diff --git a/test/test_unsigned_checked_multiplication.cpp b/test/test_unsigned_checked_multiplication.cpp new file mode 100644 index 0000000..3137c7f --- /dev/null +++ b/test/test_unsigned_checked_multiplication.cpp @@ -0,0 +1,152 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wold-style-cast" +# pragma clang diagnostic ignored "-Wundef" +# pragma clang diagnostic ignored "-Wconversion" +# pragma clang diagnostic ignored "-Wsign-conversion" +# pragma clang diagnostic ignored "-Wfloat-equal" +# pragma clang diagnostic ignored "-Wsign-compare" +# pragma clang diagnostic ignored "-Woverflow" + +# if (__clang_major__ >= 10 && !defined(__APPLE__)) || __clang_major__ >= 13 +# pragma clang diagnostic ignored "-Wdeprecated-copy" +# endif + +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wold-style-cast" +# pragma GCC diagnostic ignored "-Wundef" +# pragma GCC diagnostic ignored "-Wconversion" +# pragma GCC diagnostic ignored "-Wsign-conversion" +# pragma GCC diagnostic ignored "-Wsign-compare" +# pragma GCC diagnostic ignored "-Wfloat-equal" +# pragma GCC diagnostic ignored "-Woverflow" + +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4389) +# pragma warning(disable : 4127) +# pragma warning(disable : 4305) +# pragma warning(disable : 4309) +#endif + +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_COMPARE +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_CONVERSION + +#include + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +// Ignore [[nodiscard]] on the test that we know are going to throw +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-result" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-result" +#elif defined(_MSC_VER) +# pragma warning (push) +# pragma warning (disable: 4834) +#endif + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include +#include +#include +#include +#include + +#endif + +using namespace boost::safe_numbers; + +inline std::mt19937_64 rng{42}; +inline constexpr std::size_t N {1024}; + +template +void test_valid_multiplication() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {0, sizeof(basis_type) * 8U - 1U}; + + for (std::size_t i = 0; i < N; ++i) + { + const auto lhs_value {dist(rng)}; + const auto rhs_value {dist(rng)}; + + T ref_value {}; + if constexpr (std::is_same_v || std::is_same_v) + { + ref_value = static_cast(static_cast(static_cast(lhs_value * rhs_value))); + } + else + { + ref_value = static_cast(lhs_value * rhs_value); + } + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto res {checked_mul(lhs, rhs)}; + + BOOST_TEST(res.has_value()); + BOOST_TEST_EQ(res.value(), ref_value); + } +} + +template +void test_checked_multiplication() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {2U, std::numeric_limits::max()}; + + for (std::size_t i = 0; i < N; ++i) + { + constexpr basis_type lhs_value {std::numeric_limits::max() - 1U}; + const auto rhs_value {dist(rng)}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto res {checked_mul(lhs, rhs)}; + + BOOST_TEST(!res.has_value()); + } +} + +int main() +{ + test_valid_multiplication(); + test_checked_multiplication(); + + test_valid_multiplication(); + test_checked_multiplication(); + + test_valid_multiplication(); + test_checked_multiplication(); + + test_valid_multiplication(); + test_checked_multiplication(); + + test_valid_multiplication(); + test_checked_multiplication(); + + return boost::report_errors(); +} From 478572f02e875494c96670809ddfb48fa44dc8d8 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 3 Feb 2026 16:16:10 -0500 Subject: [PATCH 6/7] Add checked div testing --- test/Jamfile | 1 + test/test_unsigned_checked_division.cpp | 152 ++++++++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 test/test_unsigned_checked_division.cpp diff --git a/test/Jamfile b/test/Jamfile index 4b39399..a31fc15 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -83,6 +83,7 @@ run test_unsigned_overflowing_mod.cpp ; run test_unsigned_checked_addition.cpp ; run test_unsigned_checked_subtraction.cpp ; run test_unsigned_checked_multiplication.cpp ; +run test_unsigned_checked_division.cpp ; # Compile Tests compile compile_tests/compile_test_unsigned_integers.cpp ; diff --git a/test/test_unsigned_checked_division.cpp b/test/test_unsigned_checked_division.cpp new file mode 100644 index 0000000..760a46e --- /dev/null +++ b/test/test_unsigned_checked_division.cpp @@ -0,0 +1,152 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wold-style-cast" +# pragma clang diagnostic ignored "-Wundef" +# pragma clang diagnostic ignored "-Wconversion" +# pragma clang diagnostic ignored "-Wsign-conversion" +# pragma clang diagnostic ignored "-Wfloat-equal" +# pragma clang diagnostic ignored "-Wsign-compare" +# pragma clang diagnostic ignored "-Woverflow" + +# if (__clang_major__ >= 10 && !defined(__APPLE__)) || __clang_major__ >= 13 +# pragma clang diagnostic ignored "-Wdeprecated-copy" +# endif + +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wold-style-cast" +# pragma GCC diagnostic ignored "-Wundef" +# pragma GCC diagnostic ignored "-Wconversion" +# pragma GCC diagnostic ignored "-Wsign-conversion" +# pragma GCC diagnostic ignored "-Wsign-compare" +# pragma GCC diagnostic ignored "-Wfloat-equal" +# pragma GCC diagnostic ignored "-Woverflow" + +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4389) +# pragma warning(disable : 4127) +# pragma warning(disable : 4305) +# pragma warning(disable : 4309) +#endif + +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_COMPARE +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_CONVERSION + +#include + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +// Ignore [[nodiscard]] on the test that we know are going to throw +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-result" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-result" +#elif defined(_MSC_VER) +# pragma warning (push) +# pragma warning (disable: 4834) +#endif + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include +#include +#include +#include +#include + +#endif + +using namespace boost::safe_numbers; + +inline std::mt19937_64 rng{42}; +inline constexpr std::size_t N {1024}; + +template +void test_valid_division() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {1, std::numeric_limits::max()}; + + for (std::size_t i = 0; i < N; ++i) + { + const auto lhs_value {dist(rng)}; + const auto rhs_value {dist(rng)}; + + T ref_value {}; + if constexpr (std::is_same_v || std::is_same_v) + { + ref_value = static_cast(static_cast(static_cast(lhs_value / rhs_value))); + } + else + { + ref_value = static_cast(lhs_value / rhs_value); + } + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto res {checked_div(lhs, rhs)}; + + BOOST_TEST(res.has_value()); + BOOST_TEST_EQ(ref_value, res.value()); + } +} + +template +void test_throwing_division() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {1, std::numeric_limits::max()}; + + for (std::size_t i = 0; i < N; ++i) + { + const auto lhs_value {dist(rng)}; + constexpr basis_type rhs_value {0U}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto res {checked_div(lhs, rhs)}; + + BOOST_TEST(!res.has_value()); + } +} + +int main() +{ + test_valid_division(); + test_throwing_division(); + + test_valid_division(); + test_throwing_division(); + + test_valid_division(); + test_throwing_division(); + + test_valid_division(); + test_throwing_division(); + + test_valid_division(); + test_throwing_division(); + + return boost::report_errors(); +} From 4c01c17cbbf149b8813d186fb3f0bb4051416265 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 3 Feb 2026 16:17:48 -0500 Subject: [PATCH 7/7] Add testing of checked mod --- test/Jamfile | 1 + test/test_unsigned_checked_mod.cpp | 152 +++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 test/test_unsigned_checked_mod.cpp diff --git a/test/Jamfile b/test/Jamfile index a31fc15..a1e15c6 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -84,6 +84,7 @@ run test_unsigned_checked_addition.cpp ; run test_unsigned_checked_subtraction.cpp ; run test_unsigned_checked_multiplication.cpp ; run test_unsigned_checked_division.cpp ; +run test_unsigned_checked_mod.cpp ; # Compile Tests compile compile_tests/compile_test_unsigned_integers.cpp ; diff --git a/test/test_unsigned_checked_mod.cpp b/test/test_unsigned_checked_mod.cpp new file mode 100644 index 0000000..4624db7 --- /dev/null +++ b/test/test_unsigned_checked_mod.cpp @@ -0,0 +1,152 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wold-style-cast" +# pragma clang diagnostic ignored "-Wundef" +# pragma clang diagnostic ignored "-Wconversion" +# pragma clang diagnostic ignored "-Wsign-conversion" +# pragma clang diagnostic ignored "-Wfloat-equal" +# pragma clang diagnostic ignored "-Wsign-compare" +# pragma clang diagnostic ignored "-Woverflow" + +# if (__clang_major__ >= 10 && !defined(__APPLE__)) || __clang_major__ >= 13 +# pragma clang diagnostic ignored "-Wdeprecated-copy" +# endif + +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wold-style-cast" +# pragma GCC diagnostic ignored "-Wundef" +# pragma GCC diagnostic ignored "-Wconversion" +# pragma GCC diagnostic ignored "-Wsign-conversion" +# pragma GCC diagnostic ignored "-Wsign-compare" +# pragma GCC diagnostic ignored "-Wfloat-equal" +# pragma GCC diagnostic ignored "-Woverflow" + +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4389) +# pragma warning(disable : 4127) +# pragma warning(disable : 4305) +# pragma warning(disable : 4309) +#endif + +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_COMPARE +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_CONVERSION + +#include + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +// Ignore [[nodiscard]] on the test that we know are going to throw +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-result" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-result" +#elif defined(_MSC_VER) +# pragma warning (push) +# pragma warning (disable: 4834) +#endif + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include +#include +#include +#include +#include + +#endif + +using namespace boost::safe_numbers; + +inline std::mt19937_64 rng{42}; +inline constexpr std::size_t N {1024}; + +template +void test_valid_division() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {1, std::numeric_limits::max()}; + + for (std::size_t i = 0; i < N; ++i) + { + const auto lhs_value {dist(rng)}; + const auto rhs_value {dist(rng)}; + + T ref_value {}; + if constexpr (std::is_same_v || std::is_same_v) + { + ref_value = static_cast(static_cast(static_cast(lhs_value % rhs_value))); + } + else + { + ref_value = static_cast(lhs_value % rhs_value); + } + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto res {checked_mod(lhs, rhs)}; + + BOOST_TEST(res.has_value()); + BOOST_TEST_EQ(ref_value, res.value()); + } +} + +template +void test_throwing_division() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {1, std::numeric_limits::max()}; + + for (std::size_t i = 0; i < N; ++i) + { + const auto lhs_value {dist(rng)}; + constexpr basis_type rhs_value {0U}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto res {checked_mod(lhs, rhs)}; + + BOOST_TEST(!res.has_value()); + } +} + +int main() +{ + test_valid_division(); + test_throwing_division(); + + test_valid_division(); + test_throwing_division(); + + test_valid_division(); + test_throwing_division(); + + test_valid_division(); + test_throwing_division(); + + test_valid_division(); + test_throwing_division(); + + return boost::report_errors(); +}