From 887d02901461d44d53fdcb64800215d44b555370 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 2 Feb 2026 15:01:15 -0500 Subject: [PATCH 01/12] Define overflow policy enum --- .../safe_numbers/detail/overflow_policy.hpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 include/boost/safe_numbers/detail/overflow_policy.hpp diff --git a/include/boost/safe_numbers/detail/overflow_policy.hpp b/include/boost/safe_numbers/detail/overflow_policy.hpp new file mode 100644 index 0000000..642ecdc --- /dev/null +++ b/include/boost/safe_numbers/detail/overflow_policy.hpp @@ -0,0 +1,18 @@ +// 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_DETAIL_OVERFLOW_POLICY_HPP +#define BOOST_SAFE_NUMBERS_DETAIL_OVERFLOW_POLICY_HPP + +namespace boost::safe_numbers::detail { + +enum class overflow_policy +{ + throw_exception, + saturate +}; + +} // namespace boost::safe_numbers::detail + +#endif // BOOST_SAFE_NUMBERS_DETAIL_OVERFLOW_POLICY_HPP From 78d11cde2c93ab3ea2f9bee1b9f1b4cf16c9449c Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 2 Feb 2026 15:01:38 -0500 Subject: [PATCH 02/12] Use policy based design for reduced code duplication --- .../detail/unsigned_integer_basis.hpp | 193 ++++++++++++------ 1 file changed, 134 insertions(+), 59 deletions(-) diff --git a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp index 0324a57..1f2ec9a 100644 --- a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp +++ b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp @@ -7,6 +7,7 @@ #include #include +#include #ifndef BOOST_SAFE_NUMBERS_BUILD_MODULE @@ -175,9 +176,10 @@ constexpr bool unsigned_no_intrin_add(const int128::uint128_t& lhs, const int128 } // namespace impl -template -[[nodiscard]] constexpr auto operator+(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) -> unsigned_integer_basis +template +[[nodiscard]] constexpr auto add_impl(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(Policy != overflow_policy::throw_exception) -> unsigned_integer_basis { using result_type = unsigned_integer_basis; @@ -185,6 +187,18 @@ template const auto rhs_basis {static_cast(rhs)}; BasisType res {}; + auto handle_overflow = [&res] + { + if constexpr (Policy == overflow_policy::throw_exception) + { + BOOST_THROW_EXCEPTION(std::overflow_error("Overflow detected in unsigned addition")); + } + else + { + res = std::numeric_limits::max(); + } + }; + 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) @@ -193,7 +207,7 @@ template { if (impl::unsigned_intrin_add(lhs_basis, rhs_basis, res)) { - BOOST_THROW_EXCEPTION(std::overflow_error("Overflow detected in unsigned addition")); + handle_overflow(); } return result_type{res}; @@ -204,12 +218,19 @@ template if (impl::unsigned_no_intrin_add(lhs_basis, rhs_basis, res)) { - BOOST_THROW_EXCEPTION(std::overflow_error("Overflow detected in unsigned addition")); + handle_overflow(); } return result_type{res}; } +template +[[nodiscard]] constexpr auto operator+(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) -> unsigned_integer_basis +{ + return add_impl(lhs, rhs); +} + } // namespace boost::safe_numbers::detail #define BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP(OP_NAME, OP_SYMBOL) \ @@ -403,15 +424,29 @@ constexpr bool unsigned_no_intrin_sub(const int128::uint128_t& lhs, const int128 } // namespace impl -template -[[nodiscard]] constexpr auto operator-(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) -> unsigned_integer_basis +template +[[nodiscard]] constexpr auto sub_impl(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(Policy != overflow_policy::saturate) + -> unsigned_integer_basis { using result_type = unsigned_integer_basis; const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; - BasisType res; + BasisType res {}; + + auto handle_underflow = [&res] + { + if constexpr (Policy == overflow_policy::throw_exception) + { + BOOST_THROW_EXCEPTION(std::underflow_error("Underflow detected in unsigned subtraction")); + } + else + { + res = std::numeric_limits::min(); + } + }; if constexpr (!std::is_same_v) { @@ -419,9 +454,9 @@ template if (!std::is_constant_evaluated()) { - if (impl::unsigned_intrin_sub(static_cast(lhs), static_cast(rhs), res)) + if (impl::unsigned_intrin_sub(lhs_basis, rhs_basis, res)) { - BOOST_THROW_EXCEPTION(std::underflow_error("Underflow detected in unsigned subtraction")); + handle_underflow(); } return result_type{res}; @@ -432,12 +467,19 @@ template if (impl::unsigned_no_intrin_sub(lhs_basis, rhs_basis, res)) { - BOOST_THROW_EXCEPTION(std::underflow_error("Underflow detected in unsigned subtraction")); + handle_underflow(); } return result_type{res}; } +template +[[nodiscard]] constexpr auto operator-(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) -> unsigned_integer_basis +{ + return sub_impl(lhs, rhs); +} + BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("subtraction", operator-) template @@ -541,15 +583,28 @@ constexpr bool no_intrin_mul(const int128::uint128_t& lhs, const int128::uint128 } // namespace impl -template -[[nodiscard]] constexpr auto operator*(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) -> unsigned_integer_basis +template +[[nodiscard]] constexpr auto mul_impl(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(Policy == overflow_policy::saturate) + -> unsigned_integer_basis { using result_type = unsigned_integer_basis; const auto lhs_basis {static_cast(lhs)}; const auto rhs_basis {static_cast(rhs)}; - BasisType res; + BasisType res {}; + + auto handle_overflow = [&res]() { + if constexpr (Policy == overflow_policy::throw_exception) + { + BOOST_THROW_EXCEPTION(std::overflow_error("Overflow detected in unsigned multiplication")); + } + else + { + res = std::numeric_limits::max(); + } + }; if constexpr (!std::is_same_v) { @@ -559,7 +614,7 @@ template { if (impl::unsigned_intrin_mul(lhs_basis, rhs_basis, res)) { - BOOST_THROW_EXCEPTION(std::overflow_error("Overflow detected in unsigned multiplication")); + handle_overflow(); } return result_type{res}; @@ -570,12 +625,19 @@ template if (impl::no_intrin_mul(lhs_basis, rhs_basis, res)) { - BOOST_THROW_EXCEPTION(std::overflow_error("Overflow detected in unsigned multiplication")); + handle_overflow(); } return result_type{res}; } +template +[[nodiscard]] constexpr auto operator*(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) -> unsigned_integer_basis +{ + return mul_impl(lhs, rhs); +} + BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("multiplication", operator*) template @@ -591,28 +653,44 @@ constexpr auto unsigned_integer_basis::operator*=(const unsigned_inte // Division // ------------------------------ -template -[[nodiscard]] constexpr auto operator/(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) -> unsigned_integer_basis +template +[[nodiscard]] constexpr auto div_impl(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(Policy == overflow_policy::saturate) + -> unsigned_integer_basis { using result_type = unsigned_integer_basis; // Normally this should trap, but throwing an exception is more elegant if (static_cast(rhs) == 0U) [[unlikely]] { - BOOST_THROW_EXCEPTION(std::domain_error("Unsigned division by zero")); + if constexpr (Policy == overflow_policy::throw_exception) + { + BOOST_THROW_EXCEPTION(std::domain_error("Unsigned division by zero")); + } + else + { + return result_type{std::numeric_limits::max()}; + } } if constexpr (std::is_same_v || std::is_same_v) { - return static_cast(static_cast(static_cast(lhs) / static_cast(rhs))); + return result_type{static_cast(static_cast(lhs) / static_cast(rhs))}; } else { - return static_cast(static_cast(lhs) / static_cast(rhs)); + return result_type{static_cast(lhs) / static_cast(rhs)}; } } +template +[[nodiscard]] constexpr auto operator/(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) -> unsigned_integer_basis +{ + return div_impl(lhs, rhs); +} + BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("division", operator/) template @@ -628,28 +706,44 @@ constexpr auto unsigned_integer_basis::operator/=(const unsigned_inte // Modulo // ------------------------------ -template -[[nodiscard]] constexpr auto operator%(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) -> unsigned_integer_basis +template +[[nodiscard]] constexpr auto mod_impl(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(Policy == overflow_policy::saturate) + -> unsigned_integer_basis { using result_type = unsigned_integer_basis; // Normally this should trap, but throwing an exception is more elegant if (static_cast(rhs) == 0U) [[unlikely]] { - BOOST_THROW_EXCEPTION(std::domain_error("Unsigned modulo by zero")); + if constexpr (Policy == overflow_policy::throw_exception) + { + BOOST_THROW_EXCEPTION(std::domain_error("Unsigned modulo by zero")); + } + else + { + return result_type{0U}; + } } if constexpr (std::is_same_v || std::is_same_v) { - return static_cast(static_cast(static_cast(lhs) % static_cast(rhs))); + return result_type{static_cast(static_cast(lhs) % static_cast(rhs))}; } else { - return static_cast(static_cast(lhs) % static_cast(rhs)); + return result_type{static_cast(lhs) % static_cast(rhs)}; } } +template +[[nodiscard]] constexpr auto operator%(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) -> unsigned_integer_basis +{ + return mod_impl(lhs, rhs); +} + BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("modulo", operator%) template @@ -737,37 +831,18 @@ namespace boost::safe_numbers { template [[nodiscard]] constexpr auto add_sat(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept -> detail::unsigned_integer_basis + const detail::unsigned_integer_basis rhs) noexcept + -> detail::unsigned_integer_basis { - using result_type = detail::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()) - { - if (detail::impl::unsigned_intrin_add(lhs_basis, rhs_basis, res)) - { - res = std::numeric_limits::max(); - } - - return result_type{res}; - } - - #endif // __has_builtin(__builtin_add_overflow) - } - - if (detail::impl::unsigned_no_intrin_add(lhs_basis, rhs_basis, res)) - { - res = std::numeric_limits::max(); - } + return detail::add_impl(lhs, rhs); +} - return result_type{res}; +template +[[nodiscard]] constexpr auto sub_sat(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> detail::unsigned_integer_basis +{ + return detail::sub_impl(lhs, rhs); } BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating_add", add_sat) From 027789c8c7d55bfdb0ce3c6a29ba837e4e090a62 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 2 Feb 2026 15:19:22 -0500 Subject: [PATCH 03/12] Re-order tests for debugger --- test/test_unsigned_subtraction.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/test_unsigned_subtraction.cpp b/test/test_unsigned_subtraction.cpp index 657ce5d..eb4469a 100644 --- a/test/test_unsigned_subtraction.cpp +++ b/test/test_unsigned_subtraction.cpp @@ -173,24 +173,26 @@ void test_valid_compound_subtraction() int main() { test_valid_subtraction(); - test_throwing_subtraction(); test_valid_compound_subtraction(); test_valid_subtraction(); - test_throwing_subtraction(); test_valid_compound_subtraction(); test_valid_subtraction(); - test_throwing_subtraction(); test_valid_compound_subtraction(); test_valid_subtraction(); - test_throwing_subtraction(); test_valid_compound_subtraction(); test_valid_subtraction(); - test_throwing_subtraction(); test_valid_compound_subtraction(); + // These are all at the end because they kill the debugger + test_throwing_subtraction(); + test_throwing_subtraction(); + test_throwing_subtraction(); + test_throwing_subtraction(); + test_throwing_subtraction(); + return boost::report_errors(); } From e5f22d46d350ddecd8a66ac227f865b4c6c76e8a Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 2 Feb 2026 15:19:30 -0500 Subject: [PATCH 04/12] Fix exception specification --- include/boost/safe_numbers/detail/unsigned_integer_basis.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp index 1f2ec9a..d6b412f 100644 --- a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp +++ b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp @@ -427,7 +427,7 @@ constexpr bool unsigned_no_intrin_sub(const int128::uint128_t& lhs, const int128 template [[nodiscard]] constexpr auto sub_impl(const unsigned_integer_basis lhs, const unsigned_integer_basis rhs) - noexcept(Policy != overflow_policy::saturate) + noexcept(Policy != overflow_policy::throw_exception) -> unsigned_integer_basis { using result_type = unsigned_integer_basis; From 4f737c761f23c5fa14af0df99ed5591fd6c42a77 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 2 Feb 2026 15:58:08 -0500 Subject: [PATCH 05/12] Define the mixed type functions --- .../detail/unsigned_integer_basis.hpp | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp index d6b412f..b173c46 100644 --- a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp +++ b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp @@ -823,10 +823,6 @@ constexpr auto unsigned_integer_basis::operator--(int) // Saturating Math // ------------------------------ -// ------------------------------ -// add_sat -// ------------------------------ - namespace boost::safe_numbers { template @@ -837,6 +833,8 @@ template return detail::add_impl(lhs, rhs); } +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating addition", add_sat) + template [[nodiscard]] constexpr auto sub_sat(const detail::unsigned_integer_basis lhs, const detail::unsigned_integer_basis rhs) noexcept @@ -845,7 +843,27 @@ template return detail::sub_impl(lhs, rhs); } -BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating_add", add_sat) +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating subtraction", sub_sat) + +template +[[nodiscard]] constexpr auto mul_sat(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> detail::unsigned_integer_basis +{ + return detail::mul_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating multiplication", mul_sat) + +template +[[nodiscard]] constexpr auto div_sat(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> detail::unsigned_integer_basis +{ + return detail::div_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating division", div_sat) } // namespace boost::safe_numbers From fd6aa0364f89e3d5c00606a7f607719cfe84a8ad Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 2 Feb 2026 16:09:24 -0500 Subject: [PATCH 06/12] Define macro for unreachable --- include/boost/safe_numbers/detail/config.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/boost/safe_numbers/detail/config.hpp b/include/boost/safe_numbers/detail/config.hpp index 8c2d486..1c352d6 100644 --- a/include/boost/safe_numbers/detail/config.hpp +++ b/include/boost/safe_numbers/detail/config.hpp @@ -48,4 +48,12 @@ #endif +#if defined(__GNUC__) || defined(__clang__) +# define BOOST_SAFE_NUMBERS_UNREACHABLE __builtin_unreachable() +#elif defined(_MSC_VER) +# define BOOST_SAFE_NUMBERS_UNREACHABLE __assume(0) +#else +# define BOOST_SAFE_NUMBERS_UNREACHABLE std::abort() +#endif + #endif // BOOST_SAFENUMBERS_CONFIG_HPP From fa2eb4e1221c6ad21ecfc659eb36f0a7f7cf7f04 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 2 Feb 2026 16:09:41 -0500 Subject: [PATCH 07/12] Eliminate else branch of policy --- .../detail/unsigned_integer_basis.hpp | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp index b173c46..4f6af34 100644 --- a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp +++ b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp @@ -193,10 +193,14 @@ template { BOOST_THROW_EXCEPTION(std::overflow_error("Overflow detected in unsigned addition")); } - else + else if constexpr (Policy == overflow_policy::saturate) { res = std::numeric_limits::max(); } + else + { + BOOST_SAFE_NUMBERS_UNREACHABLE; + } }; if constexpr (!std::is_same_v) @@ -442,10 +446,14 @@ template { BOOST_THROW_EXCEPTION(std::underflow_error("Underflow detected in unsigned subtraction")); } - else + else if constexpr (Policy == overflow_policy::saturate) { res = std::numeric_limits::min(); } + else + { + BOOST_SAFE_NUMBERS_UNREACHABLE; + } }; if constexpr (!std::is_same_v) @@ -600,10 +608,14 @@ template { BOOST_THROW_EXCEPTION(std::overflow_error("Overflow detected in unsigned multiplication")); } - else + else if constexpr (Policy == overflow_policy::saturate) { res = std::numeric_limits::max(); } + else + { + BOOST_SAFE_NUMBERS_UNREACHABLE; + } }; if constexpr (!std::is_same_v) @@ -668,10 +680,14 @@ template { BOOST_THROW_EXCEPTION(std::domain_error("Unsigned division by zero")); } - else + else if constexpr (Policy == overflow_policy::saturate) { return result_type{std::numeric_limits::max()}; } + else + { + BOOST_SAFE_NUMBERS_UNREACHABLE; + } } if constexpr (std::is_same_v || std::is_same_v) @@ -721,10 +737,14 @@ template { BOOST_THROW_EXCEPTION(std::domain_error("Unsigned modulo by zero")); } - else + else if constexpr (Policy == overflow_policy::saturate) { return result_type{0U}; } + else + { + BOOST_SAFE_NUMBERS_UNREACHABLE; + } } if constexpr (std::is_same_v || std::is_same_v) From a73f6247e8917f1f6adf401cd0ba06ce95822d70 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Mon, 2 Feb 2026 16:10:34 -0500 Subject: [PATCH 08/12] Add testing of saturating subtraction --- test/Jamfile | 1 + test/test_unsigned_saturating_subtraction.cpp | 154 ++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 test/test_unsigned_saturating_subtraction.cpp diff --git a/test/Jamfile b/test/Jamfile index 4c8d3b7..667eaa8 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -71,6 +71,7 @@ run test_unsigned_fmt_format.cpp ; run test_unsigned_charconv.cpp ; run test_unsigned_saturating_addition.cpp ; run test_unsigned_comparisons.cpp ; +run test_unsigned_saturating_subtraction.cpp ; # Compile Tests compile compile_tests/compile_test_unsigned_integers.cpp ; diff --git a/test/test_unsigned_saturating_subtraction.cpp b/test/test_unsigned_saturating_subtraction.cpp new file mode 100644 index 0000000..4f40f34 --- /dev/null +++ b/test/test_unsigned_saturating_subtraction.cpp @@ -0,0 +1,154 @@ +// 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 T res {sub_sat(lhs, rhs)}; + + BOOST_TEST(ref_value == res); + } +} + +template +void test_saturating_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}; + + BOOST_TEST_EQ(sub_sat(lhs, rhs), std::numeric_limits::min()); + } +} + + +int main() +{ + test_valid_subtraction(); + test_valid_subtraction(); + test_valid_subtraction(); + test_valid_subtraction(); + test_valid_subtraction(); + + test_saturating_subtraction(); + test_saturating_subtraction(); + test_saturating_subtraction(); + test_saturating_subtraction(); + test_saturating_subtraction(); + + return boost::report_errors(); +} From 1a489a007a3323d93be84a27c9c70700052c266d Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 3 Feb 2026 11:19:25 -0500 Subject: [PATCH 09/12] Add mul_sat testing --- test/Jamfile | 1 + ...est_unsigned_saturating_multiplication.cpp | 150 ++++++++++++++++++ 2 files changed, 151 insertions(+) create mode 100644 test/test_unsigned_saturating_multiplication.cpp diff --git a/test/Jamfile b/test/Jamfile index 667eaa8..02550bc 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -72,6 +72,7 @@ run test_unsigned_charconv.cpp ; run test_unsigned_saturating_addition.cpp ; run test_unsigned_comparisons.cpp ; run test_unsigned_saturating_subtraction.cpp ; +run test_unsigned_saturating_multiplication.cpp ; # Compile Tests compile compile_tests/compile_test_unsigned_integers.cpp ; diff --git a/test/test_unsigned_saturating_multiplication.cpp b/test/test_unsigned_saturating_multiplication.cpp new file mode 100644 index 0000000..382d2a7 --- /dev/null +++ b/test/test_unsigned_saturating_multiplication.cpp @@ -0,0 +1,150 @@ +// 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 T res {mul_sat(lhs, rhs)}; + + BOOST_TEST(ref_value == res); + } +} + +template +void test_throwing_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}; + + BOOST_TEST_EQ(mul_sat(lhs, rhs), std::numeric_limits::max()); + } +} + +int main() +{ + test_valid_multiplication(); + test_throwing_multiplication(); + + test_valid_multiplication(); + test_throwing_multiplication(); + + test_valid_multiplication(); + test_throwing_multiplication(); + + test_valid_multiplication(); + test_throwing_multiplication(); + + test_valid_multiplication(); + test_throwing_multiplication(); + + return boost::report_errors(); +} From 742a9b5501d2d5bf5bb2827d93fdfd575645bc0f Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 3 Feb 2026 11:19:40 -0500 Subject: [PATCH 10/12] Fix unused lambda capture warning --- .../boost/safe_numbers/detail/unsigned_integer_basis.hpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp index 4f6af34..bd23b52 100644 --- a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp +++ b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp @@ -191,6 +191,7 @@ template { if constexpr (Policy == overflow_policy::throw_exception) { + static_cast(res); BOOST_THROW_EXCEPTION(std::overflow_error("Overflow detected in unsigned addition")); } else if constexpr (Policy == overflow_policy::saturate) @@ -199,6 +200,7 @@ template } else { + static_cast(res); BOOST_SAFE_NUMBERS_UNREACHABLE; } }; @@ -444,6 +446,7 @@ template { if constexpr (Policy == overflow_policy::throw_exception) { + static_cast(res); BOOST_THROW_EXCEPTION(std::underflow_error("Underflow detected in unsigned subtraction")); } else if constexpr (Policy == overflow_policy::saturate) @@ -452,6 +455,7 @@ template } else { + static_cast(res); BOOST_SAFE_NUMBERS_UNREACHABLE; } }; @@ -603,9 +607,11 @@ template const auto rhs_basis {static_cast(rhs)}; BasisType res {}; - auto handle_overflow = [&res]() { + auto handle_overflow = [&res]() + { if constexpr (Policy == overflow_policy::throw_exception) { + static_cast(res); BOOST_THROW_EXCEPTION(std::overflow_error("Overflow detected in unsigned multiplication")); } else if constexpr (Policy == overflow_policy::saturate) @@ -614,6 +620,7 @@ template } else { + static_cast(res); BOOST_SAFE_NUMBERS_UNREACHABLE; } }; From d15f0697d9b826d32c44cd20b6203a98b6d1d488 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 3 Feb 2026 11:30:54 -0500 Subject: [PATCH 11/12] Add div_sat testing --- test/Jamfile | 1 + test/test_unsigned_saturating_division.cpp | 150 +++++++++++++++++++++ 2 files changed, 151 insertions(+) create mode 100644 test/test_unsigned_saturating_division.cpp diff --git a/test/Jamfile b/test/Jamfile index 02550bc..4d9397a 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -73,6 +73,7 @@ run test_unsigned_saturating_addition.cpp ; run test_unsigned_comparisons.cpp ; run test_unsigned_saturating_subtraction.cpp ; run test_unsigned_saturating_multiplication.cpp ; +run test_unsigned_saturating_division.cpp ; # Compile Tests compile compile_tests/compile_test_unsigned_integers.cpp ; diff --git a/test/test_unsigned_saturating_division.cpp b/test/test_unsigned_saturating_division.cpp new file mode 100644 index 0000000..109447a --- /dev/null +++ b/test/test_unsigned_saturating_division.cpp @@ -0,0 +1,150 @@ +// 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 T res {div_sat(lhs, rhs)}; + + BOOST_TEST_EQ(ref_value, res); + } +} + +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}; + + BOOST_TEST_EQ(div_sat(lhs, rhs), std::numeric_limits::max()); + } +} + +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 c13b9aaecfd40659b6c3779e16d380766b99a12e Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 3 Feb 2026 11:32:28 -0500 Subject: [PATCH 12/12] Fix missing header in Clang 21 with C++23 or higher --- test/benchmarks/benchmark_unsigned_operations.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/test/benchmarks/benchmark_unsigned_operations.cpp b/test/benchmarks/benchmark_unsigned_operations.cpp index 498a678..3dd5a72 100644 --- a/test/benchmarks/benchmark_unsigned_operations.cpp +++ b/test/benchmarks/benchmark_unsigned_operations.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #if defined(__clang__) # pragma clang diagnostic push