From 2669302498361961cf6c41a6b5bb60a0b969c5b7 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 7 Apr 2026 16:24:03 -0400 Subject: [PATCH 1/5] Add promotion path for signed integers --- include/boost/safe_numbers/detail/type_traits.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/boost/safe_numbers/detail/type_traits.hpp b/include/boost/safe_numbers/detail/type_traits.hpp index 2b6a557..731ac6d 100644 --- a/include/boost/safe_numbers/detail/type_traits.hpp +++ b/include/boost/safe_numbers/detail/type_traits.hpp @@ -226,6 +226,14 @@ using promoted_type = std::conditional_t, std::u std::conditional_t, std::uint64_t, std::conditional_t, int128::uint128_t, bool>>>>; +// Promotes a signed integer to the next higher type +// int128_t becomes bool so that we can static_assert on bool check that we can't widen int128_t +template +using signed_promoted_type = std::conditional_t, std::int16_t, + std::conditional_t, std::int32_t, + std::conditional_t, std::int64_t, + std::conditional_t, int128::int128_t, bool>>>>; + } // namespace boost::safe_numbers::detail #endif // BOOST_SAFE_NUMBERS_DETAIL_TYPE_TRAITS_HPP From a787ec572858e616dbca0a23a737962a55ddb826 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 7 Apr 2026 16:24:21 -0400 Subject: [PATCH 2/5] Implement policy based free functions --- .../detail/signed_integer_basis.hpp | 916 +++++++++++++++++- 1 file changed, 892 insertions(+), 24 deletions(-) diff --git a/include/boost/safe_numbers/detail/signed_integer_basis.hpp b/include/boost/safe_numbers/detail/signed_integer_basis.hpp index e42b51a..7be595c 100644 --- a/include/boost/safe_numbers/detail/signed_integer_basis.hpp +++ b/include/boost/safe_numbers/detail/signed_integer_basis.hpp @@ -537,6 +537,22 @@ struct signed_add_helper BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, signed_underflow_add_msg()); } } + else if constexpr (Policy == overflow_policy::saturate) + { + if (status == signed_overflow_status::overflow) + { + result = std::numeric_limits::max(); + } + else + { + result = std::numeric_limits::min(); + } + } + else if constexpr (Policy == overflow_policy::strict) + { + static_cast(result); + std::exit(EXIT_FAILURE); + } else { static_cast(result); @@ -573,9 +589,95 @@ struct signed_add_helper } }; +// Partial specialization for overflow_tuple policy +template +struct signed_add_helper +{ + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) noexcept + -> std::pair, bool> + { + using result_type = signed_integer_basis; + + const auto lhs_basis {static_cast(lhs)}; + const auto rhs_basis {static_cast(rhs)}; + BasisType result {}; + + #if BOOST_SAFE_NUMBERS_HAS_BUILTIN(__builtin_add_overflow) || defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X64_INTRIN) || defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X86_INTRIN) + + if constexpr (!std::is_same_v) + { + if (!std::is_constant_evaluated()) + { + const auto status {impl::signed_intrin_add(lhs_basis, rhs_basis, result)}; + return std::make_pair(result_type{result}, status != signed_overflow_status::no_error); + } + } + + #endif + + const auto status {impl::signed_no_intrin_add(lhs_basis, rhs_basis, result)}; + return std::make_pair(result_type{result}, status != signed_overflow_status::no_error); + } +}; + +// Partial specialization for checked policy +template +struct signed_add_helper +{ + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) noexcept + -> std::optional> + { + using result_type = signed_integer_basis; + + const auto lhs_basis {static_cast(lhs)}; + const auto rhs_basis {static_cast(rhs)}; + BasisType result {}; + + #if BOOST_SAFE_NUMBERS_HAS_BUILTIN(__builtin_add_overflow) || defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X64_INTRIN) || defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X86_INTRIN) + + if constexpr (!std::is_same_v) + { + if (!std::is_constant_evaluated()) + { + const auto status {impl::signed_intrin_add(lhs_basis, rhs_basis, result)}; + return status != signed_overflow_status::no_error + ? std::nullopt + : std::make_optional(result_type{result}); + } + } + + #endif + + const auto status {impl::signed_no_intrin_add(lhs_basis, rhs_basis, result)}; + return status != signed_overflow_status::no_error + ? std::nullopt + : std::make_optional(result_type{result}); + } +}; + +// Partial specialization for widening policy +template +struct signed_add_helper +{ + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) noexcept + { + using promoted = signed_promoted_type; + static_assert(!std::is_same_v, "Widening policy with int128_t is not supported"); + + using result_type = signed_integer_basis; + return result_type{static_cast(static_cast(static_cast(lhs)) + static_cast(rhs))}; + } +}; + template [[nodiscard]] constexpr auto add_impl(const signed_integer_basis lhs, const signed_integer_basis rhs) + noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || + Policy == overflow_policy::checked || Policy == overflow_policy::strict || + Policy == overflow_policy::widen) { return signed_add_helper::apply(lhs,rhs); } @@ -1022,6 +1124,22 @@ struct signed_sub_helper BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, signed_underflow_sub_msg()); } } + else if constexpr (Policy == overflow_policy::saturate) + { + if (status == signed_overflow_status::overflow) + { + result = std::numeric_limits::max(); + } + else + { + result = std::numeric_limits::min(); + } + } + else if constexpr (Policy == overflow_policy::strict) + { + static_cast(result); + std::exit(EXIT_FAILURE); + } else { static_cast(result); @@ -1058,9 +1176,79 @@ struct signed_sub_helper } }; +// Partial specialization for overflow_tuple policy +template +struct signed_sub_helper +{ + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) noexcept + -> std::pair, bool> + { + using result_type = signed_integer_basis; + + const auto lhs_basis {static_cast(lhs)}; + const auto rhs_basis {static_cast(rhs)}; + BasisType result {}; + + #if BOOST_SAFE_NUMBERS_HAS_BUILTIN(__builtin_sub_overflow) || defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X64_INTRIN) || defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X86_INTRIN) + + if constexpr (!std::is_same_v) + { + if (!std::is_constant_evaluated()) + { + const auto status {impl::signed_intrin_sub(lhs_basis, rhs_basis, result)}; + return std::make_pair(result_type{result}, status != signed_overflow_status::no_error); + } + } + + #endif + + const auto status {impl::signed_no_intrin_sub(lhs_basis, rhs_basis, result)}; + return std::make_pair(result_type{result}, status != signed_overflow_status::no_error); + } +}; + +// Partial specialization for checked policy +template +struct signed_sub_helper +{ + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) noexcept + -> std::optional> + { + using result_type = signed_integer_basis; + + const auto lhs_basis {static_cast(lhs)}; + const auto rhs_basis {static_cast(rhs)}; + BasisType result {}; + + #if BOOST_SAFE_NUMBERS_HAS_BUILTIN(__builtin_sub_overflow) || defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X64_INTRIN) || defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X86_INTRIN) + + if constexpr (!std::is_same_v) + { + if (!std::is_constant_evaluated()) + { + const auto status {impl::signed_intrin_sub(lhs_basis, rhs_basis, result)}; + return status != signed_overflow_status::no_error + ? std::nullopt + : std::make_optional(result_type{result}); + } + } + + #endif + + const auto status {impl::signed_no_intrin_sub(lhs_basis, rhs_basis, result)}; + return status != signed_overflow_status::no_error + ? std::nullopt + : std::make_optional(result_type{result}); + } +}; + template [[nodiscard]] constexpr auto sub_impl(const signed_integer_basis lhs, const signed_integer_basis rhs) + noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || + Policy == overflow_policy::checked || Policy == overflow_policy::strict) { return signed_sub_helper::apply(lhs, rhs); } @@ -1329,13 +1517,13 @@ constexpr auto signed_no_intrin_mul(const T lhs, const T rhs, T& result) noexcep : static_cast(std::numeric_limits::max()); // Unsigned overflow check (well-tested for uint128) + // Always compute the wrapped product so overflow_tuple policy gets the correct value + result = static_cast(lhs * rhs); if (abs_rhs != unsigned_t{0} && abs_lhs > max_magnitude / abs_rhs) { - result = 0; return result_negative ? signed_overflow_status::underflow : signed_overflow_status::overflow; } - result = static_cast(lhs * rhs); return signed_overflow_status::no_error; } else if constexpr (std::is_same_v || std::is_same_v) @@ -1468,6 +1656,22 @@ struct signed_mul_helper BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, signed_underflow_mul_msg()); } } + else if constexpr (Policy == overflow_policy::saturate) + { + if (status == signed_overflow_status::overflow) + { + result = std::numeric_limits::max(); + } + else + { + result = std::numeric_limits::min(); + } + } + else if constexpr (Policy == overflow_policy::strict) + { + static_cast(result); + std::exit(EXIT_FAILURE); + } else { static_cast(result); @@ -1504,9 +1708,95 @@ struct signed_mul_helper } }; +// Partial specialization for overflow_tuple policy +template +struct signed_mul_helper +{ + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) noexcept + -> std::pair, bool> + { + using result_type = signed_integer_basis; + + const auto lhs_basis {static_cast(lhs)}; + const auto rhs_basis {static_cast(rhs)}; + BasisType result {}; + + #if BOOST_SAFE_NUMBERS_HAS_BUILTIN(__builtin_mul_overflow) || defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X64_INTRIN) || defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X86_INTRIN) + + if constexpr (!std::is_same_v) + { + if (!std::is_constant_evaluated()) + { + const auto status {impl::signed_intrin_mul(lhs_basis, rhs_basis, result)}; + return std::make_pair(result_type{result}, status != signed_overflow_status::no_error); + } + } + + #endif + + const auto status {impl::signed_no_intrin_mul(lhs_basis, rhs_basis, result)}; + return std::make_pair(result_type{result}, status != signed_overflow_status::no_error); + } +}; + +// Partial specialization for checked policy +template +struct signed_mul_helper +{ + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) noexcept + -> std::optional> + { + using result_type = signed_integer_basis; + + const auto lhs_basis {static_cast(lhs)}; + const auto rhs_basis {static_cast(rhs)}; + BasisType result {}; + + #if BOOST_SAFE_NUMBERS_HAS_BUILTIN(__builtin_mul_overflow) || defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X64_INTRIN) || defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X86_INTRIN) + + if constexpr (!std::is_same_v) + { + if (!std::is_constant_evaluated()) + { + const auto status {impl::signed_intrin_mul(lhs_basis, rhs_basis, result)}; + return status != signed_overflow_status::no_error + ? std::nullopt + : std::make_optional(result_type{result}); + } + } + + #endif + + const auto status {impl::signed_no_intrin_mul(lhs_basis, rhs_basis, result)}; + return status != signed_overflow_status::no_error + ? std::nullopt + : std::make_optional(result_type{result}); + } +}; + +// Partial specialization for widening policy +template +struct signed_mul_helper +{ + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) noexcept + { + using promoted = signed_promoted_type; + static_assert(!std::is_same_v, "Widening policy with int128_t is not supported"); + + using result_type = signed_integer_basis; + return result_type{static_cast(static_cast(static_cast(lhs)) * static_cast(rhs))}; + } +}; + template [[nodiscard]] constexpr auto mul_impl(const signed_integer_basis lhs, const signed_integer_basis rhs) + noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || + Policy == overflow_policy::checked || Policy == overflow_policy::strict || + Policy == overflow_policy::widen) { return signed_mul_helper::apply(lhs, rhs); } @@ -1646,7 +1936,7 @@ struct signed_div_helper { [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, const signed_integer_basis rhs) - noexcept(Policy != overflow_policy::throw_exception) + noexcept(Policy == overflow_policy::strict) -> signed_integer_basis { using result_type = signed_integer_basis; @@ -1685,6 +1975,14 @@ struct signed_div_helper { BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, signed_div_by_zero_msg()); } + else if constexpr (Policy == overflow_policy::saturate) + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, signed_div_by_zero_msg()); + } + else if constexpr (Policy == overflow_policy::strict) + { + std::exit(EXIT_FAILURE); + } else { BOOST_SAFE_NUMBERS_UNREACHABLE; @@ -1731,6 +2029,14 @@ struct signed_div_helper { BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, signed_overflow_div_msg()); } + else if constexpr (Policy == overflow_policy::saturate) + { + return result_type{std::numeric_limits::max()}; + } + else if constexpr (Policy == overflow_policy::strict) + { + std::exit(EXIT_FAILURE); + } else { BOOST_SAFE_NUMBERS_UNREACHABLE; @@ -1742,34 +2048,102 @@ struct signed_div_helper } }; -template -[[nodiscard]] constexpr auto div_impl(const signed_integer_basis lhs, - const signed_integer_basis rhs) +// Partial specialization for overflow_tuple policy +template +struct signed_div_helper { - return signed_div_helper::apply(lhs, rhs); -} + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) + -> std::pair, bool> + { + using result_type = signed_integer_basis; -} // namespace impl + const auto lhs_basis {static_cast(lhs)}; + const auto rhs_basis {static_cast(rhs)}; -template -[[nodiscard]] constexpr auto operator/(const signed_integer_basis lhs, - const signed_integer_basis rhs) -> signed_integer_basis -{ - return impl::signed_div_helper::apply(lhs, rhs); -} + if (rhs_basis == BasisType{0}) [[unlikely]] + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, signed_div_by_zero_msg()); + } -BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("division", operator/) + if (lhs_basis == BasisType{0}) + { + return std::make_pair(result_type{BasisType{0}}, false); + } + + if (lhs_basis == std::numeric_limits::min() && + rhs_basis == static_cast(-1)) [[unlikely]] + { + // The wrapped result is min (since -min overflows back to min in two's complement) + return std::make_pair(result_type{std::numeric_limits::min()}, true); + } + + return std::make_pair(result_type{static_cast(lhs_basis / rhs_basis)}, false); + } +}; +// Partial specialization for checked policy template -template -constexpr auto signed_integer_basis::operator/=(const signed_integer_basis rhs) - -> signed_integer_basis& +struct signed_div_helper { - *this = *this / rhs; - return *this; -} + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) noexcept + -> std::optional> + { + using result_type = signed_integer_basis; -// ------------------------------ + const auto lhs_basis {static_cast(lhs)}; + const auto rhs_basis {static_cast(rhs)}; + + if (rhs_basis == BasisType{0}) [[unlikely]] + { + return std::nullopt; + } + + if (lhs_basis == BasisType{0}) + { + return std::make_optional(result_type{BasisType{0}}); + } + + if (lhs_basis == std::numeric_limits::min() && + rhs_basis == static_cast(-1)) [[unlikely]] + { + return std::nullopt; + } + + return std::make_optional(result_type{static_cast(lhs_basis / rhs_basis)}); + } +}; + +template +[[nodiscard]] constexpr auto div_impl(const signed_integer_basis lhs, + const signed_integer_basis rhs) + noexcept(Policy == overflow_policy::checked || Policy == overflow_policy::strict) +{ + return signed_div_helper::apply(lhs, rhs); +} + +} // namespace impl + +template +[[nodiscard]] constexpr auto operator/(const signed_integer_basis lhs, + const signed_integer_basis rhs) -> signed_integer_basis +{ + return impl::signed_div_helper::apply(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("division", operator/) + +template +template +constexpr auto signed_integer_basis::operator/=(const signed_integer_basis rhs) + -> signed_integer_basis& +{ + *this = *this / rhs; + return *this; +} + +// ------------------------------ // Modulo // ------------------------------ @@ -1830,7 +2204,7 @@ struct signed_mod_helper { [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, const signed_integer_basis rhs) - noexcept(Policy != overflow_policy::throw_exception) + noexcept(Policy == overflow_policy::strict) -> signed_integer_basis { using result_type = signed_integer_basis; @@ -1869,6 +2243,14 @@ struct signed_mod_helper { BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, signed_mod_by_zero_msg()); } + else if constexpr (Policy == overflow_policy::saturate) + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, signed_mod_by_zero_msg()); + } + else if constexpr (Policy == overflow_policy::strict) + { + std::exit(EXIT_FAILURE); + } else { BOOST_SAFE_NUMBERS_UNREACHABLE; @@ -1917,6 +2299,15 @@ struct signed_mod_helper { BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, signed_overflow_mod_msg()); } + else if constexpr (Policy == overflow_policy::saturate) + { + // The mathematical result of min % -1 is 0 + return result_type{BasisType{0}}; + } + else if constexpr (Policy == overflow_policy::strict) + { + std::exit(EXIT_FAILURE); + } else { BOOST_SAFE_NUMBERS_UNREACHABLE; @@ -1928,9 +2319,77 @@ struct signed_mod_helper } }; +// Partial specialization for overflow_tuple policy +template +struct signed_mod_helper +{ + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) + -> std::pair, bool> + { + using result_type = signed_integer_basis; + + const auto lhs_basis {static_cast(lhs)}; + const auto rhs_basis {static_cast(rhs)}; + + if (rhs_basis == BasisType{0}) [[unlikely]] + { + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, signed_mod_by_zero_msg()); + } + + if (lhs_basis == BasisType{0}) + { + return std::make_pair(result_type{BasisType{0}}, false); + } + + if (lhs_basis == std::numeric_limits::min() && + rhs_basis == static_cast(-1)) [[unlikely]] + { + // The mathematical result is 0, but the implied division overflows + return std::make_pair(result_type{BasisType{0}}, true); + } + + return std::make_pair(result_type{static_cast(lhs_basis % rhs_basis)}, false); + } +}; + +// Partial specialization for checked policy +template +struct signed_mod_helper +{ + [[nodiscard]] static constexpr auto apply(const signed_integer_basis lhs, + const signed_integer_basis rhs) noexcept + -> std::optional> + { + using result_type = signed_integer_basis; + + const auto lhs_basis {static_cast(lhs)}; + const auto rhs_basis {static_cast(rhs)}; + + if (rhs_basis == BasisType{0}) [[unlikely]] + { + return std::nullopt; + } + + if (lhs_basis == BasisType{0}) + { + return std::make_optional(result_type{BasisType{0}}); + } + + if (lhs_basis == std::numeric_limits::min() && + rhs_basis == static_cast(-1)) [[unlikely]] + { + return std::nullopt; + } + + return std::make_optional(result_type{static_cast(lhs_basis % rhs_basis)}); + } +}; + template [[nodiscard]] constexpr auto mod_impl(const signed_integer_basis lhs, const signed_integer_basis rhs) + noexcept(Policy == overflow_policy::checked || Policy == overflow_policy::strict) { return signed_mod_helper::apply(lhs, rhs); } @@ -2073,6 +2532,415 @@ constexpr auto signed_integer_basis::operator--(int) } // namespace boost::safe_numbers::detail +// ------------------------------ +// Saturating Math +// ------------------------------ + +namespace boost::safe_numbers { + +template +[[nodiscard]] constexpr auto saturating_add(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> detail::signed_integer_basis +{ + return detail::impl::add_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("saturating addition", saturating_add) + +template +[[nodiscard]] constexpr auto saturating_sub(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> detail::signed_integer_basis +{ + return detail::impl::sub_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("saturating subtraction", saturating_sub) + +template +[[nodiscard]] constexpr auto saturating_mul(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> detail::signed_integer_basis +{ + return detail::impl::mul_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("saturating multiplication", saturating_mul) + +template +[[nodiscard]] constexpr auto saturating_div(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) + -> detail::signed_integer_basis +{ + return detail::impl::div_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("saturating division", saturating_div) + +template +[[nodiscard]] constexpr auto saturating_mod(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) + -> detail::signed_integer_basis +{ + return detail::impl::mod_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("saturating modulo", saturating_mod) + +// ------------------------------ +// Overflowing Math +// ------------------------------ + +template +[[nodiscard]] constexpr auto overflowing_add(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> std::pair, bool> +{ + return detail::impl::add_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("overflowing addition", overflowing_add) + +template +[[nodiscard]] constexpr auto overflowing_sub(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> std::pair, bool> +{ + return detail::impl::sub_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("overflowing subtraction", overflowing_sub) + +template +[[nodiscard]] constexpr auto overflowing_mul(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> std::pair, bool> +{ + return detail::impl::mul_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("overflowing multiplication", overflowing_mul) + +template +[[nodiscard]] constexpr auto overflowing_div(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) + -> std::pair, bool> +{ + return detail::impl::div_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("overflowing division", overflowing_div) + +template +[[nodiscard]] constexpr auto overflowing_mod(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) + -> std::pair, bool> +{ + return detail::impl::mod_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("overflowing modulo", overflowing_mod) + +// ------------------------------ +// Checked Math +// ------------------------------ + +template +[[nodiscard]] constexpr auto checked_add(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> std::optional> +{ + return detail::impl::add_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("checked addition", checked_add) + +template +[[nodiscard]] constexpr auto checked_sub(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> std::optional> +{ + return detail::impl::sub_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("checked subtraction", checked_sub) + +template +[[nodiscard]] constexpr auto checked_mul(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> std::optional> +{ + return detail::impl::mul_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("checked multiplication", checked_mul) + +template +[[nodiscard]] constexpr auto checked_div(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> std::optional> +{ + return detail::impl::div_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("checked division", checked_div) + +template +[[nodiscard]] constexpr auto checked_mod(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> std::optional> +{ + return detail::impl::mod_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("checked modulo", checked_mod) + +// ------------------------------ +// Strict Math +// ------------------------------ + +template +[[nodiscard]] constexpr auto strict_add(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> detail::signed_integer_basis +{ + return detail::impl::add_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("strict addition", strict_add) + +template +[[nodiscard]] constexpr auto strict_sub(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> detail::signed_integer_basis +{ + return detail::impl::sub_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("strict subtraction", strict_sub) + +template +[[nodiscard]] constexpr auto strict_mul(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> detail::signed_integer_basis +{ + return detail::impl::mul_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("strict multiplication", strict_mul) + +template +[[nodiscard]] constexpr auto strict_div(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> detail::signed_integer_basis +{ + return detail::impl::div_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("strict division", strict_div) + +template +[[nodiscard]] constexpr auto strict_mod(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept + -> detail::signed_integer_basis +{ + return detail::impl::mod_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("strict modulo", strict_mod) + +// ------------------------------ +// Widening Math +// ------------------------------ + +template +[[nodiscard]] constexpr auto widening_add(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept +{ + return detail::impl::add_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("widening add", widening_add) + +template +[[nodiscard]] constexpr auto widening_mul(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) noexcept +{ + return detail::impl::mul_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("widening mul", widening_mul) + +// ------------------------------ +// Generic policy-parameterized functions +// ------------------------------ + +template +[[nodiscard]] constexpr auto add(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) + noexcept(Policy != overflow_policy::throw_exception) +{ + if constexpr (Policy == overflow_policy::throw_exception) + { + return lhs + rhs; + } + else if constexpr (Policy == overflow_policy::saturate) + { + return saturating_add(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::overflow_tuple) + { + return overflowing_add(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::checked) + { + return checked_add(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::strict) + { + return strict_add(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::widen) + { + return widening_add(lhs, rhs); + } + else + { + static_assert(detail::dependent_false, "Policy is not supported for addition"); + } +} + +template +[[nodiscard]] constexpr auto sub(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) + noexcept(Policy != overflow_policy::throw_exception) +{ + if constexpr (Policy == overflow_policy::throw_exception) + { + return lhs - rhs; + } + else if constexpr (Policy == overflow_policy::saturate) + { + return saturating_sub(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::overflow_tuple) + { + return overflowing_sub(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::checked) + { + return checked_sub(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::strict) + { + return strict_sub(lhs, rhs); + } + else + { + static_assert(detail::dependent_false, "Policy is not supported for subtraction"); + } +} + +template +[[nodiscard]] constexpr auto mul(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) + noexcept(Policy != overflow_policy::throw_exception) +{ + if constexpr (Policy == overflow_policy::throw_exception) + { + return lhs * rhs; + } + else if constexpr (Policy == overflow_policy::saturate) + { + return saturating_mul(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::overflow_tuple) + { + return overflowing_mul(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::checked) + { + return checked_mul(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::strict) + { + return strict_mul(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::widen) + { + return widening_mul(lhs, rhs); + } + else + { + static_assert(detail::dependent_false, "Policy is not supported for multiplication"); + } +} + +template +[[nodiscard]] constexpr auto div(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) + noexcept(Policy == overflow_policy::checked || Policy == overflow_policy::strict) +{ + if constexpr (Policy == overflow_policy::throw_exception) + { + return lhs / rhs; + } + else if constexpr (Policy == overflow_policy::saturate) + { + return saturating_div(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::overflow_tuple) + { + return overflowing_div(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::checked) + { + return checked_div(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::strict) + { + return strict_div(lhs, rhs); + } + else + { + static_assert(detail::dependent_false, "Policy is not supported for division"); + } +} + +template +[[nodiscard]] constexpr auto mod(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis rhs) + noexcept(Policy == overflow_policy::checked || Policy == overflow_policy::strict) +{ + if constexpr (Policy == overflow_policy::throw_exception) + { + return lhs % rhs; + } + else if constexpr (Policy == overflow_policy::saturate) + { + return saturating_mod(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::overflow_tuple) + { + return overflowing_mod(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::checked) + { + return checked_mod(lhs, rhs); + } + else if constexpr (Policy == overflow_policy::strict) + { + return strict_mod(lhs, rhs); + } + else + { + static_assert(detail::dependent_false, "Policy is not supported for modulo"); + } +} + +} // namespace boost::safe_numbers + #undef BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP #endif //BOOST_SAFE_NUMBERS_SIGNED_INTEGER_BASIS_HPP From 4b58c1ee6c15162f28cbe506ab2a309fa72e09d5 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 7 Apr 2026 16:24:33 -0400 Subject: [PATCH 3/5] Test new policy based free functions --- test/Jamfile | 20 ++ test/test_signed_checked_addition.cpp | 178 +++++++++++++++++ test/test_signed_checked_division.cpp | 180 +++++++++++++++++ test/test_signed_checked_mod.cpp | 180 +++++++++++++++++ test/test_signed_checked_multiplication.cpp | 153 +++++++++++++++ test/test_signed_checked_subtraction.cpp | 178 +++++++++++++++++ test/test_signed_overflowing_addition.cpp | 178 +++++++++++++++++ test/test_signed_overflowing_division.cpp | 180 +++++++++++++++++ test/test_signed_overflowing_mod.cpp | 180 +++++++++++++++++ ...test_signed_overflowing_multiplication.cpp | 153 +++++++++++++++ test/test_signed_overflowing_subtraction.cpp | 178 +++++++++++++++++ test/test_signed_saturating_addition.cpp | 179 +++++++++++++++++ test/test_signed_saturating_division.cpp | 159 +++++++++++++++ test/test_signed_saturating_mod.cpp | 159 +++++++++++++++ .../test_signed_saturating_multiplication.cpp | 180 +++++++++++++++++ test/test_signed_saturating_subtraction.cpp | 181 ++++++++++++++++++ test/test_signed_strict_addition.cpp | 33 ++++ test/test_signed_strict_division.cpp | 33 ++++ test/test_signed_strict_mod.cpp | 33 ++++ test/test_signed_strict_multiplication.cpp | 33 ++++ test/test_signed_strict_subtraction.cpp | 33 ++++ 21 files changed, 2781 insertions(+) create mode 100644 test/test_signed_checked_addition.cpp create mode 100644 test/test_signed_checked_division.cpp create mode 100644 test/test_signed_checked_mod.cpp create mode 100644 test/test_signed_checked_multiplication.cpp create mode 100644 test/test_signed_checked_subtraction.cpp create mode 100644 test/test_signed_overflowing_addition.cpp create mode 100644 test/test_signed_overflowing_division.cpp create mode 100644 test/test_signed_overflowing_mod.cpp create mode 100644 test/test_signed_overflowing_multiplication.cpp create mode 100644 test/test_signed_overflowing_subtraction.cpp create mode 100644 test/test_signed_saturating_addition.cpp create mode 100644 test/test_signed_saturating_division.cpp create mode 100644 test/test_signed_saturating_mod.cpp create mode 100644 test/test_signed_saturating_multiplication.cpp create mode 100644 test/test_signed_saturating_subtraction.cpp create mode 100644 test/test_signed_strict_addition.cpp create mode 100644 test/test_signed_strict_division.cpp create mode 100644 test/test_signed_strict_mod.cpp create mode 100644 test/test_signed_strict_multiplication.cpp create mode 100644 test/test_signed_strict_subtraction.cpp diff --git a/test/Jamfile b/test/Jamfile index 0e34cbc..1d1ff6d 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -105,6 +105,26 @@ run-fail test_unsigned_strict_subtraction.cpp ; run-fail test_unsigned_strict_multiplication.cpp ; run-fail test_unsigned_strict_division.cpp ; run-fail test_unsigned_strict_mod.cpp ; +run test_signed_saturating_addition.cpp ; +run test_signed_saturating_subtraction.cpp ; +run test_signed_saturating_multiplication.cpp ; +run test_signed_saturating_division.cpp ; +run test_signed_saturating_mod.cpp ; +run test_signed_overflowing_addition.cpp ; +run test_signed_overflowing_subtraction.cpp ; +run test_signed_overflowing_multiplication.cpp ; +run test_signed_overflowing_division.cpp ; +run test_signed_overflowing_mod.cpp ; +run test_signed_checked_addition.cpp ; +run test_signed_checked_subtraction.cpp ; +run test_signed_checked_multiplication.cpp ; +run test_signed_checked_division.cpp ; +run test_signed_checked_mod.cpp ; +run-fail test_signed_strict_addition.cpp ; +run-fail test_signed_strict_subtraction.cpp ; +run-fail test_signed_strict_multiplication.cpp ; +run-fail test_signed_strict_division.cpp ; +run-fail test_signed_strict_mod.cpp ; # Exhaustive verification tests run test_exhaustive_u8_arithmetic.cpp ; diff --git a/test/test_signed_checked_addition.cpp b/test/test_signed_checked_addition.cpp new file mode 100644 index 0000000..52ff876 --- /dev/null +++ b/test/test_signed_checked_addition.cpp @@ -0,0 +1,178 @@ +// 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 {static_cast(std::numeric_limits::min() / 2), + static_cast(std::numeric_limits::max() / 2)}; + + 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 = T{static_cast(static_cast(lhs_value) + static_cast(rhs_value))}; + } + else + { + ref_value = T{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_overflow() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {basis_type{2}, + std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + constexpr auto lhs_value {static_cast(std::numeric_limits::max() - 1)}; + 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()); + } +} + +template +void test_checked_underflow() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {std::numeric_limits::min(), + basis_type{-2}}; + + for (std::size_t i {0}; i < N; ++i) + { + constexpr auto lhs_value {static_cast(std::numeric_limits::min() + 1)}; + 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_overflow(); + test_checked_underflow(); + + test_valid_addition(); + test_checked_overflow(); + test_checked_underflow(); + + test_valid_addition(); + test_checked_overflow(); + test_checked_underflow(); + + test_valid_addition(); + test_checked_overflow(); + test_checked_underflow(); + + test_valid_addition(); + test_checked_overflow(); + test_checked_underflow(); + + return boost::report_errors(); +} diff --git a/test/test_signed_checked_division.cpp b/test/test_signed_checked_division.cpp new file mode 100644 index 0000000..50d0f42 --- /dev/null +++ b/test/test_signed_checked_division.cpp @@ -0,0 +1,180 @@ +// 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_division() +{ + using basis_type = detail::underlying_type_t; + + // Distribute over the full range but ensure non-zero divisor and skip min/-1 + boost::random::uniform_int_distribution lhs_dist {std::numeric_limits::min(), + std::numeric_limits::max()}; + boost::random::uniform_int_distribution rhs_dist {basis_type{1}, + std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + const auto lhs_value {lhs_dist(rng)}; + const auto rhs_value {rhs_dist(rng)}; + + // rhs_dist is [1, max] so min/-1 case cannot occur + + T ref_value {}; + if constexpr (std::is_same_v || std::is_same_v) + { + ref_value = T{static_cast(static_cast(lhs_value) / static_cast(rhs_value))}; + } + else + { + ref_value = T{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_checked_div_by_zero() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {std::numeric_limits::min(), + std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + const auto lhs_value {dist(rng)}; + constexpr basis_type rhs_value {0}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto res {checked_div(lhs, rhs)}; + + BOOST_TEST(!res.has_value()); + } +} + +template +void test_checked_min_div_neg1() +{ + using basis_type = detail::underlying_type_t; + + constexpr auto lhs_value {std::numeric_limits::min()}; + constexpr basis_type rhs_value {-1}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto res {checked_div(lhs, rhs)}; + + // min / -1 overflows in two's complement, checked returns nullopt + BOOST_TEST(!res.has_value()); +} + +int main() +{ + test_valid_division(); + test_checked_div_by_zero(); + test_checked_min_div_neg1(); + + test_valid_division(); + test_checked_div_by_zero(); + test_checked_min_div_neg1(); + + test_valid_division(); + test_checked_div_by_zero(); + test_checked_min_div_neg1(); + + test_valid_division(); + test_checked_div_by_zero(); + test_checked_min_div_neg1(); + + test_valid_division(); + test_checked_div_by_zero(); + test_checked_min_div_neg1(); + + return boost::report_errors(); +} diff --git a/test/test_signed_checked_mod.cpp b/test/test_signed_checked_mod.cpp new file mode 100644 index 0000000..498babb --- /dev/null +++ b/test/test_signed_checked_mod.cpp @@ -0,0 +1,180 @@ +// 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_mod() +{ + using basis_type = detail::underlying_type_t; + + // Distribute over the full range but ensure non-zero divisor and skip min/-1 + boost::random::uniform_int_distribution lhs_dist {std::numeric_limits::min(), + std::numeric_limits::max()}; + boost::random::uniform_int_distribution rhs_dist {basis_type{1}, + std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + const auto lhs_value {lhs_dist(rng)}; + const auto rhs_value {rhs_dist(rng)}; + + // rhs_dist is [1, max] so min/-1 case cannot occur + + T ref_value {}; + if constexpr (std::is_same_v || std::is_same_v) + { + ref_value = T{static_cast(static_cast(lhs_value) % static_cast(rhs_value))}; + } + else + { + ref_value = T{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_checked_mod_by_zero() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {std::numeric_limits::min(), + std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + const auto lhs_value {dist(rng)}; + constexpr basis_type rhs_value {0}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto res {checked_mod(lhs, rhs)}; + + BOOST_TEST(!res.has_value()); + } +} + +template +void test_checked_min_mod_neg1() +{ + using basis_type = detail::underlying_type_t; + + constexpr auto lhs_value {std::numeric_limits::min()}; + constexpr basis_type rhs_value {-1}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto res {checked_mod(lhs, rhs)}; + + // min % -1 implies a division that overflows in two's complement, checked returns nullopt + BOOST_TEST(!res.has_value()); +} + +int main() +{ + test_valid_mod(); + test_checked_mod_by_zero(); + test_checked_min_mod_neg1(); + + test_valid_mod(); + test_checked_mod_by_zero(); + test_checked_min_mod_neg1(); + + test_valid_mod(); + test_checked_mod_by_zero(); + test_checked_min_mod_neg1(); + + test_valid_mod(); + test_checked_mod_by_zero(); + test_checked_min_mod_neg1(); + + test_valid_mod(); + test_checked_mod_by_zero(); + test_checked_min_mod_neg1(); + + return boost::report_errors(); +} diff --git a/test/test_signed_checked_multiplication.cpp b/test/test_signed_checked_multiplication.cpp new file mode 100644 index 0000000..9010310 --- /dev/null +++ b/test/test_signed_checked_multiplication.cpp @@ -0,0 +1,153 @@ +// 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_multiplication() +{ + using basis_type = detail::underlying_type_t; + + // Use a narrow range to avoid overflow: sizeof*8 is safe for all types + boost::random::uniform_int_distribution dist {static_cast(-(static_cast(sizeof(basis_type)) * 8)), + static_cast(sizeof(basis_type) * 8)}; + + 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 = T{static_cast(static_cast(lhs_value) * static_cast(rhs_value))}; + } + else + { + ref_value = T{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(ref_value, res.value()); + } +} + +template +void test_checked_overflow() +{ + using basis_type = detail::underlying_type_t; + + for (std::size_t i {0}; i < N; ++i) + { + constexpr auto lhs_value {std::numeric_limits::max()}; + constexpr basis_type rhs_value {2}; + + 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_overflow(); + + test_valid_multiplication(); + test_checked_overflow(); + + test_valid_multiplication(); + test_checked_overflow(); + + test_valid_multiplication(); + test_checked_overflow(); + + test_valid_multiplication(); + test_checked_overflow(); + + return boost::report_errors(); +} diff --git a/test/test_signed_checked_subtraction.cpp b/test/test_signed_checked_subtraction.cpp new file mode 100644 index 0000000..9192296 --- /dev/null +++ b/test/test_signed_checked_subtraction.cpp @@ -0,0 +1,178 @@ +// 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_subtraction() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {static_cast(std::numeric_limits::min() / 4), + static_cast(std::numeric_limits::max() / 4)}; + + 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 = T{static_cast(static_cast(lhs_value) - static_cast(rhs_value))}; + } + else + { + ref_value = T{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_EQ(ref_value, res.value()); + } +} + +template +void test_checked_overflow() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {std::numeric_limits::min(), + basis_type{-2}}; + + for (std::size_t i {0}; i < N; ++i) + { + constexpr auto lhs_value {static_cast(std::numeric_limits::max() - 1)}; + const auto rhs_value {dist(rng)}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto res {checked_sub(lhs, rhs)}; + + BOOST_TEST(!res.has_value()); + } +} + +template +void test_checked_underflow() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {basis_type{2}, + std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + constexpr auto lhs_value {static_cast(std::numeric_limits::min() + 1)}; + const auto rhs_value {dist(rng)}; + + 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_checked_overflow(); + test_checked_underflow(); + + test_valid_subtraction(); + test_checked_overflow(); + test_checked_underflow(); + + test_valid_subtraction(); + test_checked_overflow(); + test_checked_underflow(); + + test_valid_subtraction(); + test_checked_overflow(); + test_checked_underflow(); + + test_valid_subtraction(); + test_checked_overflow(); + test_checked_underflow(); + + return boost::report_errors(); +} diff --git a/test/test_signed_overflowing_addition.cpp b/test/test_signed_overflowing_addition.cpp new file mode 100644 index 0000000..10aa5cf --- /dev/null +++ b/test/test_signed_overflowing_addition.cpp @@ -0,0 +1,178 @@ +// 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 {static_cast(std::numeric_limits::min() / 2), + static_cast(std::numeric_limits::max() / 2)}; + + 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 = T{static_cast(static_cast(lhs_value) + static_cast(rhs_value))}; + } + else + { + ref_value = T{static_cast(lhs_value + rhs_value)}; + } + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto [res, overflowed] = overflowing_add(lhs, rhs); + + BOOST_TEST_EQ(ref_value, res); + BOOST_TEST(!overflowed); + } +} + +template +void test_overflowed_positive() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {basis_type{2}, + std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + constexpr auto lhs_value {static_cast(std::numeric_limits::max() - 1)}; + const auto rhs_value {dist(rng)}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto [res, overflowed] = overflowing_add(lhs, rhs); + + BOOST_TEST(overflowed); + } +} + +template +void test_overflowed_negative() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {std::numeric_limits::min(), + basis_type{-2}}; + + for (std::size_t i {0}; i < N; ++i) + { + constexpr auto lhs_value {static_cast(std::numeric_limits::min() + 1)}; + const auto rhs_value {dist(rng)}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto [res, overflowed] = overflowing_add(lhs, rhs); + + BOOST_TEST(overflowed); + } +} + +int main() +{ + test_valid_addition(); + test_overflowed_positive(); + test_overflowed_negative(); + + test_valid_addition(); + test_overflowed_positive(); + test_overflowed_negative(); + + test_valid_addition(); + test_overflowed_positive(); + test_overflowed_negative(); + + test_valid_addition(); + test_overflowed_positive(); + test_overflowed_negative(); + + test_valid_addition(); + test_overflowed_positive(); + test_overflowed_negative(); + + return boost::report_errors(); +} diff --git a/test/test_signed_overflowing_division.cpp b/test/test_signed_overflowing_division.cpp new file mode 100644 index 0000000..afaace3 --- /dev/null +++ b/test/test_signed_overflowing_division.cpp @@ -0,0 +1,180 @@ +// 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_division() +{ + using basis_type = detail::underlying_type_t; + + // Distribute over non-zero values, excluding the min/-1 case + boost::random::uniform_int_distribution lhs_dist {std::numeric_limits::min(), + std::numeric_limits::max()}; + boost::random::uniform_int_distribution rhs_dist {basis_type{1}, + std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + const auto lhs_value {lhs_dist(rng)}; + const auto rhs_value {rhs_dist(rng)}; + + // Skip the min / -1 edge case (rhs_dist is [1, max] so this won't happen anyway) + + T ref_value {}; + if constexpr (std::is_same_v || std::is_same_v) + { + ref_value = T{static_cast(static_cast(lhs_value) / static_cast(rhs_value))}; + } + else + { + ref_value = T{static_cast(lhs_value / rhs_value)}; + } + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto [res, overflowed] = overflowing_div(lhs, rhs); + + BOOST_TEST_EQ(ref_value, res); + BOOST_TEST(!overflowed); + } +} + +template +void test_div_by_zero() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {std::numeric_limits::min(), + std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + const auto lhs_value {dist(rng)}; + constexpr basis_type rhs_value {0}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + + BOOST_TEST_THROWS(overflowing_div(lhs, rhs), std::domain_error); + } +} + +template +void test_min_div_neg1() +{ + using basis_type = detail::underlying_type_t; + + constexpr auto lhs_value {std::numeric_limits::min()}; + constexpr basis_type rhs_value {-1}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto [res, overflowed] = overflowing_div(lhs, rhs); + + // The wrapped result is min (since -min overflows back to min in two's complement) + BOOST_TEST_EQ(res, T{std::numeric_limits::min()}); + BOOST_TEST(overflowed); +} + +int main() +{ + test_valid_division(); + test_div_by_zero(); + test_min_div_neg1(); + + test_valid_division(); + test_div_by_zero(); + test_min_div_neg1(); + + test_valid_division(); + test_div_by_zero(); + test_min_div_neg1(); + + test_valid_division(); + test_div_by_zero(); + test_min_div_neg1(); + + test_valid_division(); + test_div_by_zero(); + test_min_div_neg1(); + + return boost::report_errors(); +} diff --git a/test/test_signed_overflowing_mod.cpp b/test/test_signed_overflowing_mod.cpp new file mode 100644 index 0000000..9dec31d --- /dev/null +++ b/test/test_signed_overflowing_mod.cpp @@ -0,0 +1,180 @@ +// 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_mod() +{ + using basis_type = detail::underlying_type_t; + + // Distribute over the full range but ensure non-zero divisor and skip min/-1 + boost::random::uniform_int_distribution lhs_dist {std::numeric_limits::min(), + std::numeric_limits::max()}; + boost::random::uniform_int_distribution rhs_dist {basis_type{1}, + std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + const auto lhs_value {lhs_dist(rng)}; + const auto rhs_value {rhs_dist(rng)}; + + // rhs_dist is [1, max] so min/-1 case cannot occur + + T ref_value {}; + if constexpr (std::is_same_v || std::is_same_v) + { + ref_value = T{static_cast(static_cast(lhs_value) % static_cast(rhs_value))}; + } + else + { + ref_value = T{static_cast(lhs_value % rhs_value)}; + } + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto [res, overflowed] = overflowing_mod(lhs, rhs); + + BOOST_TEST_EQ(ref_value, res); + BOOST_TEST(!overflowed); + } +} + +template +void test_mod_by_zero() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {std::numeric_limits::min(), + std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + const auto lhs_value {dist(rng)}; + constexpr basis_type rhs_value {0}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + + BOOST_TEST_THROWS(overflowing_mod(lhs, rhs), std::domain_error); + } +} + +template +void test_min_mod_neg1() +{ + using basis_type = detail::underlying_type_t; + + constexpr auto lhs_value {std::numeric_limits::min()}; + constexpr basis_type rhs_value {-1}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto [res, overflowed] = overflowing_mod(lhs, rhs); + + // The mathematical result is 0, but the implied division overflows + BOOST_TEST_EQ(res, T{basis_type{0}}); + BOOST_TEST(overflowed); +} + +int main() +{ + test_valid_mod(); + test_mod_by_zero(); + test_min_mod_neg1(); + + test_valid_mod(); + test_mod_by_zero(); + test_min_mod_neg1(); + + test_valid_mod(); + test_mod_by_zero(); + test_min_mod_neg1(); + + test_valid_mod(); + test_mod_by_zero(); + test_min_mod_neg1(); + + test_valid_mod(); + test_mod_by_zero(); + test_min_mod_neg1(); + + return boost::report_errors(); +} diff --git a/test/test_signed_overflowing_multiplication.cpp b/test/test_signed_overflowing_multiplication.cpp new file mode 100644 index 0000000..bc227e7 --- /dev/null +++ b/test/test_signed_overflowing_multiplication.cpp @@ -0,0 +1,153 @@ +// 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_multiplication() +{ + using basis_type = detail::underlying_type_t; + + // Use a narrow range to avoid overflow: sizeof*8 is safe for all types + boost::random::uniform_int_distribution dist {static_cast(-(static_cast(sizeof(basis_type)) * 8)), + static_cast(sizeof(basis_type) * 8)}; + + 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 = T{static_cast(static_cast(lhs_value) * static_cast(rhs_value))}; + } + else + { + ref_value = T{static_cast(lhs_value * rhs_value)}; + } + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto [res, overflowed] = overflowing_mul(lhs, rhs); + + BOOST_TEST_EQ(ref_value, res); + BOOST_TEST(!overflowed); + } +} + +template +void test_overflowed_multiplication() +{ + using basis_type = detail::underlying_type_t; + + for (std::size_t i {0}; i < N; ++i) + { + constexpr auto lhs_value {std::numeric_limits::max()}; + constexpr basis_type rhs_value {2}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto [res, overflowed] = overflowing_mul(lhs, rhs); + + BOOST_TEST(overflowed); + } +} + +int main() +{ + test_valid_multiplication(); + test_overflowed_multiplication(); + + test_valid_multiplication(); + test_overflowed_multiplication(); + + test_valid_multiplication(); + test_overflowed_multiplication(); + + test_valid_multiplication(); + test_overflowed_multiplication(); + + test_valid_multiplication(); + test_overflowed_multiplication(); + + return boost::report_errors(); +} diff --git a/test/test_signed_overflowing_subtraction.cpp b/test/test_signed_overflowing_subtraction.cpp new file mode 100644 index 0000000..24b8a88 --- /dev/null +++ b/test/test_signed_overflowing_subtraction.cpp @@ -0,0 +1,178 @@ +// 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_subtraction() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {static_cast(std::numeric_limits::min() / 4), + static_cast(std::numeric_limits::max() / 4)}; + + 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 = T{static_cast(static_cast(lhs_value) - static_cast(rhs_value))}; + } + else + { + ref_value = T{static_cast(lhs_value - rhs_value)}; + } + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto [res, overflowed] = overflowing_sub(lhs, rhs); + + BOOST_TEST_EQ(ref_value, res); + BOOST_TEST(!overflowed); + } +} + +template +void test_overflowed_positive() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {std::numeric_limits::min(), + basis_type{-2}}; + + for (std::size_t i {0}; i < N; ++i) + { + constexpr auto lhs_value {static_cast(std::numeric_limits::max() - 1)}; + const auto rhs_value {dist(rng)}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto [res, overflowed] = overflowing_sub(lhs, rhs); + + BOOST_TEST(overflowed); + } +} + +template +void test_overflowed_negative() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {basis_type{2}, + std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + constexpr auto lhs_value {static_cast(std::numeric_limits::min() + 1)}; + const auto rhs_value {dist(rng)}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto [res, overflowed] = overflowing_sub(lhs, rhs); + + BOOST_TEST(overflowed); + } +} + +int main() +{ + test_valid_subtraction(); + test_overflowed_positive(); + test_overflowed_negative(); + + test_valid_subtraction(); + test_overflowed_positive(); + test_overflowed_negative(); + + test_valid_subtraction(); + test_overflowed_positive(); + test_overflowed_negative(); + + test_valid_subtraction(); + test_overflowed_positive(); + test_overflowed_negative(); + + test_valid_subtraction(); + test_overflowed_positive(); + test_overflowed_negative(); + + return boost::report_errors(); +} diff --git a/test/test_signed_saturating_addition.cpp b/test/test_signed_saturating_addition.cpp new file mode 100644 index 0000000..fe5734a --- /dev/null +++ b/test/test_signed_saturating_addition.cpp @@ -0,0 +1,179 @@ +// 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 {static_cast(std::numeric_limits::min() / 2), + static_cast(std::numeric_limits::max() / 2)}; + + 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 = T{static_cast(static_cast(lhs_value) + static_cast(rhs_value))}; + } + else + { + ref_value = T{static_cast(lhs_value + rhs_value)}; + } + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const T res {saturating_add(lhs, rhs)}; + + BOOST_TEST_EQ(ref_value, res); + } +} + +template +void test_saturated_overflow() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {basis_type{2}, + std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + constexpr T max_value {std::numeric_limits::max()}; + constexpr auto lhs_value {static_cast(std::numeric_limits::max() - 1)}; + const auto rhs_value {dist(rng)}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const T res {saturating_add(lhs, rhs)}; + + BOOST_TEST_EQ(res, max_value); + } +} + +template +void test_saturated_underflow() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {std::numeric_limits::min(), + basis_type{-2}}; + + for (std::size_t i {0}; i < N; ++i) + { + constexpr T min_value {std::numeric_limits::min()}; + constexpr auto lhs_value {static_cast(std::numeric_limits::min() + 1)}; + const auto rhs_value {dist(rng)}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const T res {saturating_add(lhs, rhs)}; + + BOOST_TEST_EQ(res, min_value); + } +} + +int main() +{ + test_valid_addition(); + test_saturated_overflow(); + test_saturated_underflow(); + + test_valid_addition(); + test_saturated_overflow(); + test_saturated_underflow(); + + test_valid_addition(); + test_saturated_overflow(); + test_saturated_underflow(); + + test_valid_addition(); + test_saturated_overflow(); + test_saturated_underflow(); + + test_valid_addition(); + test_saturated_overflow(); + test_saturated_underflow(); + + return boost::report_errors(); +} diff --git a/test/test_signed_saturating_division.cpp b/test/test_signed_saturating_division.cpp new file mode 100644 index 0000000..299f6ee --- /dev/null +++ b/test/test_signed_saturating_division.cpp @@ -0,0 +1,159 @@ +// 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 num_dist {std::numeric_limits::min(), + std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + const auto lhs_value {num_dist(rng)}; + auto rhs_value {num_dist(rng)}; + + // Ensure non-zero divisor + while (rhs_value == basis_type{0}) + { + rhs_value = num_dist(rng); + } + + // Skip the min / -1 overflow case + if (lhs_value == std::numeric_limits::min() && rhs_value == static_cast(-1)) + { + continue; + } + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const T ref {static_cast(lhs_value / rhs_value)}; + const T res {saturating_div(lhs, rhs)}; + + BOOST_TEST_EQ(ref, res); + } +} + +template +void test_div_by_zero() +{ + const T one {1}; + const T zero {0}; + BOOST_TEST_THROWS(saturating_div(one, zero), std::domain_error); +} + +template +void test_min_div_neg1() +{ + using basis_type = detail::underlying_type_t; + const T min_val {std::numeric_limits::min()}; + const T neg_one {static_cast(-1)}; + const T max_val {std::numeric_limits::max()}; + BOOST_TEST_EQ(saturating_div(min_val, neg_one), max_val); +} + +int main() +{ + test_valid_division(); + test_div_by_zero(); + test_min_div_neg1(); + + test_valid_division(); + test_div_by_zero(); + test_min_div_neg1(); + + test_valid_division(); + test_div_by_zero(); + test_min_div_neg1(); + + test_valid_division(); + test_div_by_zero(); + test_min_div_neg1(); + + test_valid_division(); + test_div_by_zero(); + test_min_div_neg1(); + + return boost::report_errors(); +} diff --git a/test/test_signed_saturating_mod.cpp b/test/test_signed_saturating_mod.cpp new file mode 100644 index 0000000..e000e40 --- /dev/null +++ b/test/test_signed_saturating_mod.cpp @@ -0,0 +1,159 @@ +// 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_mod() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution num_dist {std::numeric_limits::min(), + std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + const auto lhs_value {num_dist(rng)}; + auto rhs_value {num_dist(rng)}; + + // Ensure non-zero divisor + while (rhs_value == basis_type{0}) + { + rhs_value = num_dist(rng); + } + + // Skip the min % -1 case (handled separately) + if (lhs_value == std::numeric_limits::min() && rhs_value == static_cast(-1)) + { + continue; + } + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const T ref {static_cast(lhs_value % rhs_value)}; + const T res {saturating_mod(lhs, rhs)}; + + BOOST_TEST_EQ(ref, res); + } +} + +template +void test_mod_by_zero() +{ + const T one {1}; + const T zero {0}; + BOOST_TEST_THROWS(saturating_mod(one, zero), std::domain_error); +} + +template +void test_min_mod_neg1() +{ + using basis_type = detail::underlying_type_t; + const T min_val {std::numeric_limits::min()}; + const T neg_one {static_cast(-1)}; + const T expected_zero {0}; + BOOST_TEST_EQ(saturating_mod(min_val, neg_one), expected_zero); +} + +int main() +{ + test_valid_mod(); + test_mod_by_zero(); + test_min_mod_neg1(); + + test_valid_mod(); + test_mod_by_zero(); + test_min_mod_neg1(); + + test_valid_mod(); + test_mod_by_zero(); + test_min_mod_neg1(); + + test_valid_mod(); + test_mod_by_zero(); + test_min_mod_neg1(); + + test_valid_mod(); + test_mod_by_zero(); + test_min_mod_neg1(); + + return boost::report_errors(); +} diff --git a/test/test_signed_saturating_multiplication.cpp b/test/test_signed_saturating_multiplication.cpp new file mode 100644 index 0000000..6391883 --- /dev/null +++ b/test/test_signed_saturating_multiplication.cpp @@ -0,0 +1,180 @@ +// 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; + // Use a narrow range to avoid overflow: [-128, 127] for small types works well + // For all types, values in [-sizeof*8, sizeof*8] are safe to multiply without overflow + boost::random::uniform_int_distribution dist {static_cast(-(static_cast(sizeof(basis_type)) * 8)), + static_cast(sizeof(basis_type) * 8)}; + + 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 = T{static_cast(static_cast(lhs_value) * static_cast(rhs_value))}; + } + else + { + ref_value = T{static_cast(lhs_value * rhs_value)}; + } + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const T res {saturating_mul(lhs, rhs)}; + + BOOST_TEST(ref_value == res); + } +} + +template +void test_saturating_positive_overflow() +{ + using basis_type = detail::underlying_type_t; + // max * 2 should saturate to max + boost::random::uniform_int_distribution dist {basis_type{2}, + std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + constexpr basis_type lhs_value {std::numeric_limits::max() - 1}; + const auto rhs_value {dist(rng)}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + + BOOST_TEST_EQ(saturating_mul(lhs, rhs), std::numeric_limits::max()); + } +} + +template +void test_saturating_negative_overflow() +{ + using basis_type = detail::underlying_type_t; + // max * (large negative) should saturate to min + boost::random::uniform_int_distribution dist {std::numeric_limits::min(), + basis_type{-2}}; + + for (std::size_t i {0}; i < N; ++i) + { + constexpr basis_type lhs_value {std::numeric_limits::max() - 1}; + const auto rhs_value {dist(rng)}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + + BOOST_TEST_EQ(saturating_mul(lhs, rhs), std::numeric_limits::min()); + } +} + +int main() +{ + test_valid_multiplication(); + test_saturating_positive_overflow(); + test_saturating_negative_overflow(); + + test_valid_multiplication(); + test_saturating_positive_overflow(); + test_saturating_negative_overflow(); + + test_valid_multiplication(); + test_saturating_positive_overflow(); + test_saturating_negative_overflow(); + + test_valid_multiplication(); + test_saturating_positive_overflow(); + test_saturating_negative_overflow(); + + test_valid_multiplication(); + test_saturating_positive_overflow(); + test_saturating_negative_overflow(); + + return boost::report_errors(); +} diff --git a/test/test_signed_saturating_subtraction.cpp b/test/test_signed_saturating_subtraction.cpp new file mode 100644 index 0000000..b455c3c --- /dev/null +++ b/test/test_signed_saturating_subtraction.cpp @@ -0,0 +1,181 @@ +// 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_subtraction() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {static_cast(std::numeric_limits::min() / 2), + static_cast(std::numeric_limits::max() / 2)}; + + 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 = T{static_cast(static_cast(lhs_value) - static_cast(rhs_value))}; + } + else + { + ref_value = T{static_cast(lhs_value - rhs_value)}; + } + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const T res {saturating_sub(lhs, rhs)}; + + BOOST_TEST_EQ(ref_value, res); + } +} + +template +void test_saturated_overflow() +{ + using basis_type = detail::underlying_type_t; + // max - (large negative) overflows positively + boost::random::uniform_int_distribution dist {std::numeric_limits::min(), + basis_type{-2}}; + + for (std::size_t i {0}; i < N; ++i) + { + constexpr T max_value {std::numeric_limits::max()}; + constexpr auto lhs_value {static_cast(std::numeric_limits::max() - 1)}; + const auto rhs_value {dist(rng)}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const T res {saturating_sub(lhs, rhs)}; + + BOOST_TEST_EQ(res, max_value); + } +} + +template +void test_saturated_underflow() +{ + using basis_type = detail::underlying_type_t; + // min - (large positive) underflows negatively + boost::random::uniform_int_distribution dist {basis_type{2}, + std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + constexpr T min_value {std::numeric_limits::min()}; + constexpr auto lhs_value {static_cast(std::numeric_limits::min() + 1)}; + const auto rhs_value {dist(rng)}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const T res {saturating_sub(lhs, rhs)}; + + BOOST_TEST_EQ(res, min_value); + } +} + +int main() +{ + test_valid_subtraction(); + test_saturated_overflow(); + test_saturated_underflow(); + + test_valid_subtraction(); + test_saturated_overflow(); + test_saturated_underflow(); + + test_valid_subtraction(); + test_saturated_overflow(); + test_saturated_underflow(); + + test_valid_subtraction(); + test_saturated_overflow(); + test_saturated_underflow(); + + test_valid_subtraction(); + test_saturated_overflow(); + test_saturated_underflow(); + + return boost::report_errors(); +} diff --git a/test/test_signed_strict_addition.cpp b/test/test_signed_strict_addition.cpp new file mode 100644 index 0000000..e554f03 --- /dev/null +++ b/test/test_signed_strict_addition.cpp @@ -0,0 +1,33 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// This test verifies that strict_add calls std::exit(EXIT_FAILURE) on overflow +// It is marked as run-fail in the Jamfile + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include + +#endif + +using namespace boost::safe_numbers; + +int main() +{ + // Create values that will overflow when added + constexpr i32 max_val {std::numeric_limits::max()}; + constexpr i32 one {1}; + + // This should call std::exit(EXIT_FAILURE) + const auto result {strict_add(max_val, one)}; + + // Should never reach here + static_cast(result); + return 0; // LCOV_EXCL_LINE +} diff --git a/test/test_signed_strict_division.cpp b/test/test_signed_strict_division.cpp new file mode 100644 index 0000000..1eb43ee --- /dev/null +++ b/test/test_signed_strict_division.cpp @@ -0,0 +1,33 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// This test verifies that strict_div calls std::exit(EXIT_FAILURE) on overflow +// It is marked as run-fail in the Jamfile + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include + +#endif + +using namespace boost::safe_numbers; + +int main() +{ + // min / -1 overflows for signed integers + constexpr i32 min_val {std::numeric_limits::min()}; + constexpr i32 neg_one {-1}; + + // This should call std::exit(EXIT_FAILURE) + const auto result {strict_div(min_val, neg_one)}; + + // Should never reach here + static_cast(result); + return 0; // LCOV_EXCL_LINE +} diff --git a/test/test_signed_strict_mod.cpp b/test/test_signed_strict_mod.cpp new file mode 100644 index 0000000..706da42 --- /dev/null +++ b/test/test_signed_strict_mod.cpp @@ -0,0 +1,33 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// This test verifies that strict_mod calls std::exit(EXIT_FAILURE) on overflow +// It is marked as run-fail in the Jamfile + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include + +#endif + +using namespace boost::safe_numbers; + +int main() +{ + // min % -1 overflows for signed integers + constexpr i32 min_val {std::numeric_limits::min()}; + constexpr i32 neg_one {-1}; + + // This should call std::exit(EXIT_FAILURE) + const auto result {strict_mod(min_val, neg_one)}; + + // Should never reach here + static_cast(result); + return 0; // LCOV_EXCL_LINE +} diff --git a/test/test_signed_strict_multiplication.cpp b/test/test_signed_strict_multiplication.cpp new file mode 100644 index 0000000..7357412 --- /dev/null +++ b/test/test_signed_strict_multiplication.cpp @@ -0,0 +1,33 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// This test verifies that strict_mul calls std::exit(EXIT_FAILURE) on overflow +// It is marked as run-fail in the Jamfile + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include + +#endif + +using namespace boost::safe_numbers; + +int main() +{ + // Create values that will overflow when multiplied + constexpr i32 max_val {std::numeric_limits::max()}; + constexpr i32 two {2}; + + // This should call std::exit(EXIT_FAILURE) + const auto result {strict_mul(max_val, two)}; + + // Should never reach here + static_cast(result); + return 0; // LCOV_EXCL_LINE +} diff --git a/test/test_signed_strict_subtraction.cpp b/test/test_signed_strict_subtraction.cpp new file mode 100644 index 0000000..74b5f5b --- /dev/null +++ b/test/test_signed_strict_subtraction.cpp @@ -0,0 +1,33 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// This test verifies that strict_sub calls std::exit(EXIT_FAILURE) on overflow +// It is marked as run-fail in the Jamfile + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include + +#endif + +using namespace boost::safe_numbers; + +int main() +{ + // Create values that will overflow when subtracted + constexpr i32 min_val {std::numeric_limits::min()}; + constexpr i32 one {1}; + + // This should call std::exit(EXIT_FAILURE) + const auto result {strict_sub(min_val, one)}; + + // Should never reach here + static_cast(result); + return 0; // LCOV_EXCL_LINE +} From 8cbb6186710955ddc9bb1f2f8807bf4570f3cc27 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 7 Apr 2026 16:25:06 -0400 Subject: [PATCH 4/5] Update documentation with new signed free functions --- doc/modules/ROOT/pages/signed_integers.adoc | 88 +++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/doc/modules/ROOT/pages/signed_integers.adoc b/doc/modules/ROOT/pages/signed_integers.adoc index f3e2998..cdf4b96 100644 --- a/doc/modules/ROOT/pages/signed_integers.adoc +++ b/doc/modules/ROOT/pages/signed_integers.adoc @@ -148,3 +148,91 @@ auto b = i32{200}; // auto result = a + b; // Compile error: mismatched types auto result = static_cast(a) + b; // OK: explicit promotion ---- + +== Policy-Based Arithmetic Functions + +In addition to the default operators (which throw on overflow/underflow), signed integer types support the same policy-based free functions as unsigned types. +See xref:policies.adoc[] for a detailed description of each overflow policy. + +=== Saturating Arithmetic + +Saturating operations clamp the result to `std::numeric_limits::max()` on overflow or `std::numeric_limits::min()` on underflow, rather than throwing. + +[source,c++] +---- +template constexpr T saturating_add(T lhs, T rhs) noexcept; +template constexpr T saturating_sub(T lhs, T rhs) noexcept; +template constexpr T saturating_mul(T lhs, T rhs) noexcept; +template constexpr T saturating_div(T lhs, T rhs); // throws on div-by-zero +template constexpr T saturating_mod(T lhs, T rhs); // throws on mod-by-zero +---- + +NOTE: Division by zero always throws `std::domain_error`, even with the saturating policy. The `min / -1` overflow case saturates to `max()`. The `min % -1` case returns `0`. + +=== Overflowing Arithmetic + +Overflowing operations return a `std::pair` where the bool indicates whether overflow or underflow occurred. The result wraps on overflow (two's complement behavior). + +[source,c++] +---- +template constexpr std::pair overflowing_add(T lhs, T rhs) noexcept; +template constexpr std::pair overflowing_sub(T lhs, T rhs) noexcept; +template constexpr std::pair overflowing_mul(T lhs, T rhs) noexcept; +template constexpr std::pair overflowing_div(T lhs, T rhs); // throws on div-by-zero +template constexpr std::pair overflowing_mod(T lhs, T rhs); // throws on mod-by-zero +---- + +NOTE: Division by zero still throws `std::domain_error`. The `min / -1` case returns `(min, true)`. The `min % -1` case returns `(0, true)`. + +=== Checked Arithmetic + +Checked operations return `std::optional`. If the operation would overflow, underflow, or divide by zero, `std::nullopt` is returned. + +[source,c++] +---- +template constexpr std::optional checked_add(T lhs, T rhs) noexcept; +template constexpr std::optional checked_sub(T lhs, T rhs) noexcept; +template constexpr std::optional checked_mul(T lhs, T rhs) noexcept; +template constexpr std::optional checked_div(T lhs, T rhs) noexcept; +template constexpr std::optional checked_mod(T lhs, T rhs) noexcept; +---- + +NOTE: All checked operations are `noexcept`. Division by zero and `min / -1` both return `std::nullopt`. + +=== Strict Arithmetic + +Strict operations call `std::exit(EXIT_FAILURE)` on overflow, underflow, or (for div/mod) division by zero. + +[source,c++] +---- +template constexpr T strict_add(T lhs, T rhs) noexcept; +template constexpr T strict_sub(T lhs, T rhs) noexcept; +template constexpr T strict_mul(T lhs, T rhs) noexcept; +template constexpr T strict_div(T lhs, T rhs) noexcept; +template constexpr T strict_mod(T lhs, T rhs) noexcept; +---- + +=== Widening Arithmetic + +Widening operations return the result in the next wider signed integer type, guaranteeing no overflow. Only available for addition and multiplication. + +[source,c++] +---- +template constexpr auto widening_add(T lhs, T rhs) noexcept; +template constexpr auto widening_mul(T lhs, T rhs) noexcept; +---- + +NOTE: Widening is not supported for `i128` (there is no wider signed type). Attempting to use it with `i128` is a compile-time error. + +=== Generic Policy-Parameterized Arithmetic + +These functions accept an `overflow_policy` template parameter and dispatch to the corresponding named function above. + +[source,c++] +---- +template constexpr auto add(T lhs, T rhs); +template constexpr auto sub(T lhs, T rhs); +template constexpr auto mul(T lhs, T rhs); +template constexpr auto div(T lhs, T rhs); +template constexpr auto mod(T lhs, T rhs); +---- From e7b18766a7dc53243beb7794df5f92054d6b35c4 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Tue, 7 Apr 2026 16:46:31 -0400 Subject: [PATCH 5/5] Fix GCC -Wterminate warnings --- .../detail/signed_integer_basis.hpp | 234 ++++++++---------- 1 file changed, 105 insertions(+), 129 deletions(-) diff --git a/include/boost/safe_numbers/detail/signed_integer_basis.hpp b/include/boost/safe_numbers/detail/signed_integer_basis.hpp index 7be595c..736dc4a 100644 --- a/include/boost/safe_numbers/detail/signed_integer_basis.hpp +++ b/include/boost/safe_numbers/detail/signed_integer_basis.hpp @@ -1946,46 +1946,38 @@ struct signed_div_helper if (rhs_basis == BasisType{0}) [[unlikely]] { - if (std::is_constant_evaluated()) + if constexpr (Policy == overflow_policy::strict) { - if constexpr (std::is_same_v) - { - throw std::domain_error("Division by zero in i8 division"); - } - else if constexpr (std::is_same_v) - { - throw std::domain_error("Division by zero in i16 division"); - } - else if constexpr (std::is_same_v) - { - throw std::domain_error("Division by zero in i32 division"); - } - else if constexpr (std::is_same_v) - { - throw std::domain_error("Division by zero in i64 division"); - } - else - { - throw std::domain_error("Division by zero in i128 division"); - } + std::exit(EXIT_FAILURE); } else { - if constexpr (Policy == overflow_policy::throw_exception) - { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, signed_div_by_zero_msg()); - } - else if constexpr (Policy == overflow_policy::saturate) - { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, signed_div_by_zero_msg()); - } - else if constexpr (Policy == overflow_policy::strict) + if (std::is_constant_evaluated()) { - std::exit(EXIT_FAILURE); + if constexpr (std::is_same_v) + { + throw std::domain_error("Division by zero in i8 division"); + } + else if constexpr (std::is_same_v) + { + throw std::domain_error("Division by zero in i16 division"); + } + else if constexpr (std::is_same_v) + { + throw std::domain_error("Division by zero in i32 division"); + } + else if constexpr (std::is_same_v) + { + throw std::domain_error("Division by zero in i64 division"); + } + else + { + throw std::domain_error("Division by zero in i128 division"); + } } else { - BOOST_SAFE_NUMBERS_UNREACHABLE; + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, signed_div_by_zero_msg()); } } } @@ -2000,46 +1992,42 @@ struct signed_div_helper if (lhs_basis == std::numeric_limits::min() && rhs_basis == static_cast(-1)) [[unlikely]] { - if (std::is_constant_evaluated()) + if constexpr (Policy == overflow_policy::strict) { - if constexpr (std::is_same_v) - { - throw std::overflow_error("Overflow detected in i8 division"); - } - else if constexpr (std::is_same_v) - { - throw std::overflow_error("Overflow detected in i16 division"); - } - else if constexpr (std::is_same_v) - { - throw std::overflow_error("Overflow detected in i32 division"); - } - else if constexpr (std::is_same_v) - { - throw std::overflow_error("Overflow detected in i64 division"); - } - else - { - throw std::overflow_error("Overflow detected in i128 division"); - } + std::exit(EXIT_FAILURE); + } + else if constexpr (Policy == overflow_policy::saturate) + { + return result_type{std::numeric_limits::max()}; } else { - if constexpr (Policy == overflow_policy::throw_exception) - { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, signed_overflow_div_msg()); - } - else if constexpr (Policy == overflow_policy::saturate) + if (std::is_constant_evaluated()) { - return result_type{std::numeric_limits::max()}; - } - else if constexpr (Policy == overflow_policy::strict) - { - std::exit(EXIT_FAILURE); + if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in i8 division"); + } + else if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in i16 division"); + } + else if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in i32 division"); + } + else if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in i64 division"); + } + else + { + throw std::overflow_error("Overflow detected in i128 division"); + } } else { - BOOST_SAFE_NUMBERS_UNREACHABLE; + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, signed_overflow_div_msg()); } } } @@ -2214,46 +2202,38 @@ struct signed_mod_helper if (rhs_basis == BasisType{0}) [[unlikely]] { - if (std::is_constant_evaluated()) + if constexpr (Policy == overflow_policy::strict) { - if constexpr (std::is_same_v) - { - throw std::domain_error("Division by zero in i8 modulo"); - } - else if constexpr (std::is_same_v) - { - throw std::domain_error("Division by zero in i16 modulo"); - } - else if constexpr (std::is_same_v) - { - throw std::domain_error("Division by zero in i32 modulo"); - } - else if constexpr (std::is_same_v) - { - throw std::domain_error("Division by zero in i64 modulo"); - } - else - { - throw std::domain_error("Division by zero in i128 modulo"); - } + std::exit(EXIT_FAILURE); } else { - if constexpr (Policy == overflow_policy::throw_exception) - { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, signed_mod_by_zero_msg()); - } - else if constexpr (Policy == overflow_policy::saturate) - { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, signed_mod_by_zero_msg()); - } - else if constexpr (Policy == overflow_policy::strict) + if (std::is_constant_evaluated()) { - std::exit(EXIT_FAILURE); + if constexpr (std::is_same_v) + { + throw std::domain_error("Division by zero in i8 modulo"); + } + else if constexpr (std::is_same_v) + { + throw std::domain_error("Division by zero in i16 modulo"); + } + else if constexpr (std::is_same_v) + { + throw std::domain_error("Division by zero in i32 modulo"); + } + else if constexpr (std::is_same_v) + { + throw std::domain_error("Division by zero in i64 modulo"); + } + else + { + throw std::domain_error("Division by zero in i128 modulo"); + } } else { - BOOST_SAFE_NUMBERS_UNREACHABLE; + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::domain_error, signed_mod_by_zero_msg()); } } } @@ -2270,47 +2250,43 @@ struct signed_mod_helper if (lhs_basis == std::numeric_limits::min() && rhs_basis == static_cast(-1)) [[unlikely]] { - if (std::is_constant_evaluated()) + if constexpr (Policy == overflow_policy::strict) { - if constexpr (std::is_same_v) - { - throw std::overflow_error("Overflow detected in i8 modulo"); - } - else if constexpr (std::is_same_v) - { - throw std::overflow_error("Overflow detected in i16 modulo"); - } - else if constexpr (std::is_same_v) - { - throw std::overflow_error("Overflow detected in i32 modulo"); - } - else if constexpr (std::is_same_v) - { - throw std::overflow_error("Overflow detected in i64 modulo"); - } - else - { - throw std::overflow_error("Overflow detected in i128 modulo"); - } + std::exit(EXIT_FAILURE); + } + else if constexpr (Policy == overflow_policy::saturate) + { + // The mathematical result of min % -1 is 0 + return result_type{BasisType{0}}; } else { - if constexpr (Policy == overflow_policy::throw_exception) - { - BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, signed_overflow_mod_msg()); - } - else if constexpr (Policy == overflow_policy::saturate) + if (std::is_constant_evaluated()) { - // The mathematical result of min % -1 is 0 - return result_type{BasisType{0}}; - } - else if constexpr (Policy == overflow_policy::strict) - { - std::exit(EXIT_FAILURE); + if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in i8 modulo"); + } + else if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in i16 modulo"); + } + else if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in i32 modulo"); + } + else if constexpr (std::is_same_v) + { + throw std::overflow_error("Overflow detected in i64 modulo"); + } + else + { + throw std::overflow_error("Overflow detected in i128 modulo"); + } } else { - BOOST_SAFE_NUMBERS_UNREACHABLE; + BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, signed_overflow_mod_msg()); } } }