diff --git a/doc/modules/ROOT/pages/unsigned_integers.adoc b/doc/modules/ROOT/pages/unsigned_integers.adoc index febb856..4c096fb 100644 --- a/doc/modules/ROOT/pages/unsigned_integers.adoc +++ b/doc/modules/ROOT/pages/unsigned_integers.adoc @@ -58,7 +58,8 @@ Narrowing conversions cause a compile-time error. == Comparison Operators -Full three-way comparison is supported via `operator<=>`, which returns `std::strong_ordering`. All comparison operators (`<`, `<=`, `>`, `>=`, `==`, `!=`) are available. +Full three-way comparison is supported via `pass:[operator<=>]`, which returns `std::strong_ordering`. +All comparison operators (`<`, `<=`, `>`, `>=`, `==`, `!=`) are available. == Arithmetic Operators @@ -103,32 +104,121 @@ Operations between different width safe integer types are compile-time errors. T For cases where throwing exceptions is not desired, saturating arithmetic functions are provided. These functions clamp the result to the representable range instead of throwing. -=== add_sat +[source, c++] +---- +namespace boost::safe_numbers { + +template +constexpr T saturating_add(const T lhs, const T rhs) noexcept; + +template +constexpr T saturating_sub(const T lhs, const T rhs) noexcept; + +template +constexpr T saturating_mul(const T lhs, const T rhs) noexcept; + +template +constexpr T saturating_div(const T lhs, const T rhs); + +template +constexpr T saturating_mod(const T lhs, const T rhs); + +} // namespace boost::safe_numbers +---- + +=== saturating_add + +Returns the sum of two values, saturating at the maximum value on overflow (i.e., returns `std::numeric_limits::max`). + +=== saturating_sub + +Returns the difference of two values, saturating at the minimum value on underflow (i.e., returns `std::numeric_limits::min`). + +=== saturating_mul + +Returns the product of two values, saturating at the maximum value on overflow (i.e., returns `std::numeric_limits::max`). + +=== saturating_div + +Returns the quotient of two values, and throws `std::range_error` in the event of division by 0. +Otherwise, overflow and underflow are impossible. + +=== saturating_div + +Returns the remainder of two values, and throws `std::range_error` in the event of division by 0. +Otherwise, overflow and underflow are impossible. + +== Overflowing Arithmetic + +This family of functions provides well-defined wrapping semantics as well as a flag to alert the consumer if overflow occurred. +This follows normal C family unsigned rollover where `UINT_MAX + 1 == 0` and `UINT_MIN - 1 == UINT_MAX`. + +[source, c++] +---- +namespace boost::safe_numbers { + +template +constexpr std::pair overflowing_add(const T lhs, const T rhs) noexcept; + +template +constexpr std::pair overflowing_sub(const T lhs, const T rhs) noexcept; + +template +constexpr std::pair overflowing_mul(const T lhs, const T rhs) noexcept; + +template +constexpr std::pair overflowing_div(const T lhs, const T rhs); + +template +constexpr std::pair overflowing_mod(const T lhs, const T rhs); + +} // namespace boost::safe_numbers +---- + +=== overflowing_add + +Returns the sum of two values, wrapping the sum and setting the `bool` in the return to `true` as required. + +=== overflowing_sub + +Returns the difference of two values, wrapping the difference and setting the `bool` in the return to `true` as required. + +=== overflowing_mul + +Returns the product of two values, wrapping the product and setting the `bool` in the return to `true` as required. + +=== overflowing_div + +Returns the quotient of two values, and throws `std::range_error` in the event of division by 0. +Otherwise, overflow and underflow are impossible. + +=== overflowing_mod -Returns the sum of two values, saturating at the maximum value on overflow. +Returns the remainder of two values, and throws `std::range_error` in the event of division by 0. +Otherwise, overflow and underflow are impossible. == Exception Summary |=== | Operation | Exception Type | Condition -| `+`, `+=` +| `pass:[+]`, `pass:[+=]` | `std::overflow_error` -| Result exceeds maximum value +| The result exceeds maximum value | `-`, `-=` | `std::underflow_error` -| Result would be negative +| The result would be negative -| `*`, `*=` +| `pass:[*]`, `pass:[*=]` | `std::overflow_error` -| Result exceeds maximum value +| The result exceeds maximum value -| `/`, `/=` +| `/`, `/=`, `saturating_div` | `std::domain_error` | Division by zero -| `%`, `%=` +| `%`, `%=`, `saturating_mod` | `std::domain_error` | Modulo by zero @@ -143,4 +233,5 @@ Returns the sum of two values, saturating at the maximum value on overflow. == Constexpr Support -All operations are `constexpr`-compatible when evaluated at compile time. Overflow at compile time results in a compile error. +All operations are `constexpr`-compatible. +Overflow at compile time results in a compiler error. diff --git a/include/boost/safe_numbers/detail/int128/format.hpp b/include/boost/safe_numbers/detail/int128/format.hpp index e64e36f..9c70e8d 100644 --- a/include/boost/safe_numbers/detail/int128/format.hpp +++ b/include/boost/safe_numbers/detail/int128/format.hpp @@ -12,6 +12,7 @@ #include #include #include +#include #define BOOST_SAFE_NUMBERS_DETAIL_INT128_HAS_FORMAT diff --git a/include/boost/safe_numbers/detail/overflow_policy.hpp b/include/boost/safe_numbers/detail/overflow_policy.hpp index 642ecdc..185127c 100644 --- a/include/boost/safe_numbers/detail/overflow_policy.hpp +++ b/include/boost/safe_numbers/detail/overflow_policy.hpp @@ -10,7 +10,8 @@ namespace boost::safe_numbers::detail { enum class overflow_policy { throw_exception, - saturate + saturate, + overflow_tuple, }; } // namespace boost::safe_numbers::detail diff --git a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp index bd23b52..eab8458 100644 --- a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp +++ b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #endif // BOOST_SAFE_NUMBERS_BUILD_MODULE @@ -176,58 +177,103 @@ constexpr bool unsigned_no_intrin_add(const int128::uint128_t& lhs, const int128 } // namespace impl +// Primary template for non-tuple policies template -[[nodiscard]] constexpr auto add_impl(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) - noexcept(Policy != overflow_policy::throw_exception) -> unsigned_integer_basis +struct add_helper { - using result_type = unsigned_integer_basis; + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(Policy != overflow_policy::throw_exception) + -> unsigned_integer_basis + { + using result_type = unsigned_integer_basis; - const auto lhs_basis {static_cast(lhs)}; - const auto rhs_basis {static_cast(rhs)}; - BasisType res {}; + const auto lhs_basis {static_cast(lhs)}; + const auto rhs_basis {static_cast(rhs)}; + BasisType res {}; - auto handle_overflow = [&res] - { - if constexpr (Policy == overflow_policy::throw_exception) + auto handle_overflow = [&res] { - static_cast(res); - BOOST_THROW_EXCEPTION(std::overflow_error("Overflow detected in unsigned addition")); - } - else if constexpr (Policy == overflow_policy::saturate) + if constexpr (Policy == overflow_policy::throw_exception) + { + static_cast(res); + BOOST_THROW_EXCEPTION(std::overflow_error("Overflow detected in unsigned addition")); + } + else if constexpr (Policy == overflow_policy::saturate) + { + res = std::numeric_limits::max(); + } + else + { + static_cast(res); + BOOST_SAFE_NUMBERS_UNREACHABLE; + } + }; + + if constexpr (!std::is_same_v) { - res = std::numeric_limits::max(); + #if BOOST_SAFE_NUMBERS_HAS_BUILTIN(__builtin_add_overflow) || BOOST_SAFE_NUMBERS_HAS_BUILTIN(_addcarry_u64) || defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X86_INTRIN) + + if (!std::is_constant_evaluated()) + { + if (impl::unsigned_intrin_add(lhs_basis, rhs_basis, res)) + { + handle_overflow(); + } + + return result_type{res}; + } + + #endif } - else + + if (impl::unsigned_no_intrin_add(lhs_basis, rhs_basis, res)) { - static_cast(res); - BOOST_SAFE_NUMBERS_UNREACHABLE; + handle_overflow(); } - }; - if constexpr (!std::is_same_v) + return result_type{res}; + } +}; + +// Partial specialization for overflow_tuple policy +template +struct add_helper +{ + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> std::pair, bool> { - #if BOOST_SAFE_NUMBERS_HAS_BUILTIN(__builtin_add_overflow) || BOOST_SAFE_NUMBERS_HAS_BUILTIN(_addcarry_u64) || defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X86_INTRIN) + using result_type = unsigned_integer_basis; + + const auto lhs_basis {static_cast(lhs)}; + const auto rhs_basis {static_cast(rhs)}; + BasisType res {}; - if (!std::is_constant_evaluated()) + if constexpr (!std::is_same_v) { - if (impl::unsigned_intrin_add(lhs_basis, rhs_basis, res)) + #if BOOST_SAFE_NUMBERS_HAS_BUILTIN(__builtin_add_overflow) || BOOST_SAFE_NUMBERS_HAS_BUILTIN(_addcarry_u64) || defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X86_INTRIN) + + if (!std::is_constant_evaluated()) { - handle_overflow(); + const auto overflowed {impl::unsigned_intrin_add(lhs_basis, rhs_basis, res)}; + return std::make_pair(result_type{res}, overflowed); } - return result_type{res}; + #endif } - #endif // __has_builtin(__builtin_add_overflow) - } - - if (impl::unsigned_no_intrin_add(lhs_basis, rhs_basis, res)) - { - handle_overflow(); + const auto overflowed {impl::unsigned_no_intrin_add(lhs_basis, rhs_basis, res)}; + return std::make_pair(result_type{res}, overflowed); } +}; - return result_type{res}; +template +[[nodiscard]] constexpr auto add_impl(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple) +{ + return add_helper::apply(lhs, rhs); } template @@ -430,59 +476,103 @@ constexpr bool unsigned_no_intrin_sub(const int128::uint128_t& lhs, const int128 } // namespace impl +// Primary template for non-tuple policies template -[[nodiscard]] constexpr auto sub_impl(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) - noexcept(Policy != overflow_policy::throw_exception) - -> unsigned_integer_basis +struct sub_helper { - using result_type = unsigned_integer_basis; + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(Policy != overflow_policy::throw_exception) + -> unsigned_integer_basis + { + using result_type = unsigned_integer_basis; - const auto lhs_basis {static_cast(lhs)}; - const auto rhs_basis {static_cast(rhs)}; - BasisType res {}; + const auto lhs_basis {static_cast(lhs)}; + const auto rhs_basis {static_cast(rhs)}; + BasisType res {}; - auto handle_underflow = [&res] - { - if constexpr (Policy == overflow_policy::throw_exception) + auto handle_underflow = [&res] { - static_cast(res); - BOOST_THROW_EXCEPTION(std::underflow_error("Underflow detected in unsigned subtraction")); - } - else if constexpr (Policy == overflow_policy::saturate) + if constexpr (Policy == overflow_policy::throw_exception) + { + static_cast(res); + BOOST_THROW_EXCEPTION(std::underflow_error("Underflow detected in unsigned subtraction")); + } + else if constexpr (Policy == overflow_policy::saturate) + { + res = std::numeric_limits::min(); + } + else + { + static_cast(res); + BOOST_SAFE_NUMBERS_UNREACHABLE; + } + }; + + if constexpr (!std::is_same_v) { - res = std::numeric_limits::min(); + #if BOOST_SAFE_NUMBERS_HAS_BUILTIN(__builtin_sub_overflow) || BOOST_SAFE_NUMBERS_HAS_BUILTIN(_subborrow_u64) || defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X86_INTRIN) + + if (!std::is_constant_evaluated()) + { + if (impl::unsigned_intrin_sub(lhs_basis, rhs_basis, res)) + { + handle_underflow(); + } + + return result_type{res}; + } + + #endif } - else + + if (impl::unsigned_no_intrin_sub(lhs_basis, rhs_basis, res)) { - static_cast(res); - BOOST_SAFE_NUMBERS_UNREACHABLE; + handle_underflow(); } - }; - if constexpr (!std::is_same_v) + return result_type{res}; + } +}; + +// Partial specialization for overflow_tuple policy +template +struct sub_helper +{ + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> std::pair, bool> { - #if BOOST_SAFE_NUMBERS_HAS_BUILTIN(__builtin_sub_overflow) || BOOST_SAFE_NUMBERS_HAS_BUILTIN(_subborrow_u64) || defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X86_INTRIN) + using result_type = unsigned_integer_basis; - if (!std::is_constant_evaluated()) + const auto lhs_basis {static_cast(lhs)}; + const auto rhs_basis {static_cast(rhs)}; + BasisType res {}; + + if constexpr (!std::is_same_v) { - if (impl::unsigned_intrin_sub(lhs_basis, rhs_basis, res)) + #if BOOST_SAFE_NUMBERS_HAS_BUILTIN(__builtin_sub_overflow) || BOOST_SAFE_NUMBERS_HAS_BUILTIN(_subborrow_u64) || defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X86_INTRIN) + + if (!std::is_constant_evaluated()) { - handle_underflow(); + const auto underflowed {impl::unsigned_intrin_sub(lhs_basis, rhs_basis, res)}; + return std::make_pair(result_type{res}, underflowed); } - return result_type{res}; + #endif } - #endif // Use builtins - } - - if (impl::unsigned_no_intrin_sub(lhs_basis, rhs_basis, res)) - { - handle_underflow(); + const auto underflowed {impl::unsigned_no_intrin_sub(lhs_basis, rhs_basis, res)}; + return std::make_pair(result_type{res}, underflowed); } +}; - return result_type{res}; +template +[[nodiscard]] constexpr auto sub_impl(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple) +{ + return sub_helper::apply(lhs, rhs); } template @@ -580,74 +670,109 @@ constexpr bool no_intrin_mul(const T lhs, const T rhs, T& result) constexpr bool no_intrin_mul(const int128::uint128_t& lhs, const int128::uint128_t& rhs, int128::uint128_t& result) noexcept { - // Fall back to division check - if (rhs != 0U && lhs > (std::numeric_limits::max() / rhs)) - { - result = std::numeric_limits::max(); - return true; - } - else - { - result = lhs * rhs; - return false; - } + result = lhs * rhs; + return rhs != 0U && lhs > (std::numeric_limits::max() / rhs); } } // namespace impl +// Primary template for non-tuple policies template -[[nodiscard]] constexpr auto mul_impl(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) - noexcept(Policy == overflow_policy::saturate) - -> unsigned_integer_basis +struct mul_helper { - using result_type = unsigned_integer_basis; + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(Policy == overflow_policy::saturate) + -> unsigned_integer_basis + { + using result_type = unsigned_integer_basis; - const auto lhs_basis {static_cast(lhs)}; - const auto rhs_basis {static_cast(rhs)}; - BasisType res {}; + const auto lhs_basis {static_cast(lhs)}; + const auto rhs_basis {static_cast(rhs)}; + BasisType res {}; - auto handle_overflow = [&res]() - { - if constexpr (Policy == overflow_policy::throw_exception) + auto handle_overflow = [&res] { - static_cast(res); - BOOST_THROW_EXCEPTION(std::overflow_error("Overflow detected in unsigned multiplication")); - } - else if constexpr (Policy == overflow_policy::saturate) + if constexpr (Policy == overflow_policy::throw_exception) + { + static_cast(res); + BOOST_THROW_EXCEPTION(std::overflow_error("Overflow detected in unsigned multiplication")); + } + else if constexpr (Policy == overflow_policy::saturate) + { + res = std::numeric_limits::max(); + } + else + { + static_cast(res); + BOOST_SAFE_NUMBERS_UNREACHABLE; + } + }; + + if constexpr (!std::is_same_v) { - res = std::numeric_limits::max(); + #if BOOST_SAFE_NUMBERS_HAS_BUILTIN(__builtin_mul_overflow) || BOOST_SAFE_NUMBERS_HAS_BUILTIN(_umul128) + + if (!std::is_constant_evaluated()) + { + if (impl::unsigned_intrin_mul(lhs_basis, rhs_basis, res)) + { + handle_overflow(); + } + + return result_type{res}; + } + + #endif } - else + + if (impl::no_intrin_mul(lhs_basis, rhs_basis, res)) { - static_cast(res); - BOOST_SAFE_NUMBERS_UNREACHABLE; + handle_overflow(); } - }; - if constexpr (!std::is_same_v) + return result_type{res}; + } +}; + +// Partial specialization for overflow_tuple policy +template +struct mul_helper +{ + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> std::pair, bool> { - #if BOOST_SAFE_NUMBERS_HAS_BUILTIN(__builtin_mul_overflow) || BOOST_SAFE_NUMBERS_HAS_BUILTIN(_umul128) + using result_type = unsigned_integer_basis; - if (!std::is_constant_evaluated()) + const auto lhs_basis {static_cast(lhs)}; + const auto rhs_basis {static_cast(rhs)}; + BasisType res {}; + + if constexpr (!std::is_same_v) { - if (impl::unsigned_intrin_mul(lhs_basis, rhs_basis, res)) + #if BOOST_SAFE_NUMBERS_HAS_BUILTIN(__builtin_mul_overflow) || BOOST_SAFE_NUMBERS_HAS_BUILTIN(_umul128) + + if (!std::is_constant_evaluated()) { - handle_overflow(); + const auto overflowed {impl::unsigned_intrin_mul(lhs_basis, rhs_basis, res)}; + return std::make_pair(result_type{res}, overflowed); } - return result_type{res}; + #endif } - #endif // Use builtins - } - - if (impl::no_intrin_mul(lhs_basis, rhs_basis, res)) - { - handle_overflow(); + const auto overflowed {impl::no_intrin_mul(lhs_basis, rhs_basis, res)}; + return std::make_pair(result_type{res}, overflowed); } +}; - return result_type{res}; +template +[[nodiscard]] constexpr auto mul_impl(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple) +{ + return mul_helper::apply(lhs, rhs); } template @@ -672,39 +797,75 @@ constexpr auto unsigned_integer_basis::operator*=(const unsigned_inte // Division // ------------------------------ +// Primary template for non-tuple policies template -[[nodiscard]] constexpr auto div_impl(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) - noexcept(Policy == overflow_policy::saturate) - -> unsigned_integer_basis +struct div_helper { - using result_type = unsigned_integer_basis; - - // Normally this should trap, but throwing an exception is more elegant - if (static_cast(rhs) == 0U) [[unlikely]] + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + -> unsigned_integer_basis { - if constexpr (Policy == overflow_policy::throw_exception) + using result_type = unsigned_integer_basis; + + if (static_cast(rhs) == 0U) [[unlikely]] { - BOOST_THROW_EXCEPTION(std::domain_error("Unsigned division by zero")); + if constexpr (Policy == overflow_policy::throw_exception) + { + BOOST_THROW_EXCEPTION(std::domain_error("Unsigned division by zero")); + } + else if constexpr (Policy == overflow_policy::saturate) + { + BOOST_THROW_EXCEPTION(std::domain_error("Unsigned division by zero")); + } + else + { + BOOST_SAFE_NUMBERS_UNREACHABLE; + } } - else if constexpr (Policy == overflow_policy::saturate) + + if constexpr (std::is_same_v || std::is_same_v) { - return result_type{std::numeric_limits::max()}; + return result_type{static_cast(static_cast(lhs) / static_cast(rhs))}; } else { - BOOST_SAFE_NUMBERS_UNREACHABLE; + return result_type{static_cast(lhs) / static_cast(rhs)}; } } +}; - if constexpr (std::is_same_v || std::is_same_v) - { - return result_type{static_cast(static_cast(lhs) / static_cast(rhs))}; - } - else +// Partial specialization for overflow_tuple policy +template +struct div_helper +{ + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + -> std::pair, bool> { - return result_type{static_cast(lhs) / static_cast(rhs)}; + using result_type = unsigned_integer_basis; + + const auto divisor {static_cast(rhs)}; + if (divisor == 0U) [[unlikely]] + { + BOOST_THROW_EXCEPTION(std::domain_error("Unsigned division by zero")); + } + + if constexpr (std::is_same_v || std::is_same_v) + { + return std::make_pair(result_type{static_cast(static_cast(lhs) / divisor)}, false); + } + else + { + return std::make_pair(result_type{static_cast(lhs) / divisor}, false); + } } +}; + +template +[[nodiscard]] constexpr auto div_impl(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) +{ + return div_helper::apply(lhs, rhs); } template @@ -729,39 +890,75 @@ constexpr auto unsigned_integer_basis::operator/=(const unsigned_inte // Modulo // ------------------------------ +// Primary template for non-tuple policies template -[[nodiscard]] constexpr auto mod_impl(const unsigned_integer_basis lhs, - const unsigned_integer_basis rhs) - noexcept(Policy == overflow_policy::saturate) - -> unsigned_integer_basis +struct mod_helper { - using result_type = unsigned_integer_basis; - - // Normally this should trap, but throwing an exception is more elegant - if (static_cast(rhs) == 0U) [[unlikely]] + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + -> unsigned_integer_basis { - if constexpr (Policy == overflow_policy::throw_exception) + using result_type = unsigned_integer_basis; + + if (static_cast(rhs) == 0U) [[unlikely]] { - BOOST_THROW_EXCEPTION(std::domain_error("Unsigned modulo by zero")); + if constexpr (Policy == overflow_policy::throw_exception) + { + BOOST_THROW_EXCEPTION(std::domain_error("Unsigned modulo by zero")); + } + else if constexpr (Policy == overflow_policy::saturate) + { + BOOST_THROW_EXCEPTION(std::domain_error("Unsigned division by zero")); + } + else + { + BOOST_SAFE_NUMBERS_UNREACHABLE; + } } - else if constexpr (Policy == overflow_policy::saturate) + + if constexpr (std::is_same_v || std::is_same_v) { - return result_type{0U}; + return result_type{static_cast(static_cast(lhs) % static_cast(rhs))}; } else { - BOOST_SAFE_NUMBERS_UNREACHABLE; + return result_type{static_cast(lhs) % static_cast(rhs)}; } } +}; - if constexpr (std::is_same_v || std::is_same_v) - { - return result_type{static_cast(static_cast(lhs) % static_cast(rhs))}; - } - else +// Partial specialization for overflow_tuple policy +template +struct mod_helper +{ + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) + -> std::pair, bool> { - return result_type{static_cast(lhs) % static_cast(rhs)}; + using result_type = unsigned_integer_basis; + + const auto divisor {static_cast(rhs)}; + if (divisor == 0U) [[unlikely]] + { + BOOST_THROW_EXCEPTION(std::domain_error("Unsigned division by zero")); + } + + if constexpr (std::is_same_v || std::is_same_v) + { + return std::make_pair(result_type{static_cast(static_cast(lhs) % divisor)}, false); + } + else + { + return std::make_pair(result_type{static_cast(lhs) % divisor}, false); + } } +}; + +template +[[nodiscard]] constexpr auto mod_impl(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) +{ + return mod_helper::apply(lhs, rhs); } template @@ -853,44 +1050,94 @@ constexpr auto unsigned_integer_basis::operator--(int) namespace boost::safe_numbers { template -[[nodiscard]] constexpr auto add_sat(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept +[[nodiscard]] constexpr auto saturating_add(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept -> detail::unsigned_integer_basis { return detail::add_impl(lhs, rhs); } -BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating addition", add_sat) +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating addition", saturating_add) template -[[nodiscard]] constexpr auto sub_sat(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept +[[nodiscard]] constexpr auto saturating_sub(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept -> detail::unsigned_integer_basis { return detail::sub_impl(lhs, rhs); } -BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating subtraction", sub_sat) +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating subtraction", saturating_sub) template -[[nodiscard]] constexpr auto mul_sat(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept +[[nodiscard]] constexpr auto saturating_mul(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept -> detail::unsigned_integer_basis { return detail::mul_impl(lhs, rhs); } -BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating multiplication", mul_sat) +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating multiplication", saturating_mul) template -[[nodiscard]] constexpr auto div_sat(const detail::unsigned_integer_basis lhs, - const detail::unsigned_integer_basis rhs) noexcept +[[nodiscard]] constexpr auto saturating_div(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) -> detail::unsigned_integer_basis { return detail::div_impl(lhs, rhs); } -BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating division", div_sat) +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating division", saturating_div) + +template +[[nodiscard]] constexpr auto saturating_mod(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) + -> detail::unsigned_integer_basis +{ + return detail::mod_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating modulo", saturating_mod) + +template +[[nodiscard]] constexpr auto overflowing_add(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::pair, bool> +{ + return detail::add_impl(lhs, rhs); +} + +template +[[nodiscard]] constexpr auto overflowing_sub(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::pair, bool> +{ + return detail::sub_impl(lhs, rhs); +} + +template +[[nodiscard]] constexpr auto overflowing_mul(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::pair, bool> +{ + return detail::mul_impl(lhs, rhs); +} + +template +[[nodiscard]] constexpr auto overflowing_div(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) + -> std::pair, bool> +{ + return detail::div_impl(lhs, rhs); +} + +template +[[nodiscard]] constexpr auto overflowing_mod(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) + -> std::pair, bool> +{ + return detail::mod_impl(lhs, rhs); +} } // namespace boost::safe_numbers diff --git a/test/Jamfile b/test/Jamfile index 4d9397a..da32648 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -74,6 +74,12 @@ run test_unsigned_comparisons.cpp ; run test_unsigned_saturating_subtraction.cpp ; run test_unsigned_saturating_multiplication.cpp ; run test_unsigned_saturating_division.cpp ; +run test_unsigned_saturating_mod.cpp ; +run test_unsigned_overflowing_addition.cpp ; +run test_unsigned_overflowing_subtraction.cpp ; +run test_unsigned_overflowing_multiplication.cpp ; +run test_unsigned_overflowing_division.cpp ; +run test_unsigned_overflowing_mod.cpp ; # Compile Tests compile compile_tests/compile_test_unsigned_integers.cpp ; diff --git a/test/test_unsigned_overflowing_addition.cpp b/test/test_unsigned_overflowing_addition.cpp new file mode 100644 index 0000000..4ff2c4b --- /dev/null +++ b/test/test_unsigned_overflowing_addition.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_addition() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {std::numeric_limits::min() / 2U, + std::numeric_limits::max() / 2U}; + + for (std::size_t i = 0; i < N; ++i) + { + const auto lhs_value {dist(rng)}; + const auto rhs_value {dist(rng)}; + + T ref_value {}; + if constexpr (std::is_same_v || std::is_same_v) + { + ref_value = static_cast(static_cast(static_cast(lhs_value + rhs_value))); + } + else + { + ref_value = static_cast(lhs_value + rhs_value); + } + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto [res, overflowed] = overflowing_add(lhs, rhs); + + BOOST_TEST_EQ(ref_value, res); + BOOST_TEST(!overflowed); + } +} + +template +void test_overflowed_addition() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {2U, std::numeric_limits::max()}; + + for (std::size_t i = 0; i < N; ++i) + { + constexpr basis_type lhs_value {std::numeric_limits::max()}; + 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_EQ(res, T{static_cast(std::numeric_limits::max() + rhs_value)}); + BOOST_TEST(overflowed); + } +} + +int main() +{ + test_valid_addition(); + test_overflowed_addition(); + + test_valid_addition(); + test_overflowed_addition(); + + test_valid_addition(); + test_overflowed_addition(); + + test_valid_addition(); + test_overflowed_addition(); + + test_valid_addition(); + test_overflowed_addition(); + + return boost::report_errors(); +} diff --git a/test/test_unsigned_overflowing_division.cpp b/test/test_unsigned_overflowing_division.cpp new file mode 100644 index 0000000..1695615 --- /dev/null +++ b/test/test_unsigned_overflowing_division.cpp @@ -0,0 +1,150 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wold-style-cast" +# pragma clang diagnostic ignored "-Wundef" +# pragma clang diagnostic ignored "-Wconversion" +# pragma clang diagnostic ignored "-Wsign-conversion" +# pragma clang diagnostic ignored "-Wfloat-equal" +# pragma clang diagnostic ignored "-Wsign-compare" +# pragma clang diagnostic ignored "-Woverflow" + +# if (__clang_major__ >= 10 && !defined(__APPLE__)) || __clang_major__ >= 13 +# pragma clang diagnostic ignored "-Wdeprecated-copy" +# endif + +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wold-style-cast" +# pragma GCC diagnostic ignored "-Wundef" +# pragma GCC diagnostic ignored "-Wconversion" +# pragma GCC diagnostic ignored "-Wsign-conversion" +# pragma GCC diagnostic ignored "-Wsign-compare" +# pragma GCC diagnostic ignored "-Wfloat-equal" +# pragma GCC diagnostic ignored "-Woverflow" + +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4389) +# pragma warning(disable : 4127) +# pragma warning(disable : 4305) +# pragma warning(disable : 4309) +#endif + +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_COMPARE +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_CONVERSION + +#include + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +// Ignore [[nodiscard]] on the test that we know are going to throw +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-result" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-result" +#elif defined(_MSC_VER) +# pragma warning (push) +# pragma warning (disable: 4834) +#endif + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include +#include +#include +#include +#include + +#endif + +using namespace boost::safe_numbers; + +inline std::mt19937_64 rng{42}; +inline constexpr std::size_t N {1024}; + +template +void test_valid_division() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {1, std::numeric_limits::max()}; + + for (std::size_t i = 0; i < N; ++i) + { + const auto lhs_value {dist(rng)}; + const auto rhs_value {dist(rng)}; + + T ref_value {}; + if constexpr (std::is_same_v || std::is_same_v) + { + ref_value = static_cast(static_cast(static_cast(lhs_value / rhs_value))); + } + else + { + ref_value = static_cast(lhs_value / rhs_value); + } + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto [res, overflowed] = overflowing_div(lhs, rhs); + + BOOST_TEST_EQ(ref_value, res); + } +} + +template +void test_throwing_division() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {1, std::numeric_limits::max()}; + + for (std::size_t i = 0; i < N; ++i) + { + const auto lhs_value {dist(rng)}; + constexpr basis_type rhs_value {0U}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + + BOOST_TEST_THROWS(overflowing_div(lhs, rhs), std::domain_error); + } +} + +int main() +{ + test_valid_division(); + test_throwing_division(); + + test_valid_division(); + test_throwing_division(); + + test_valid_division(); + test_throwing_division(); + + test_valid_division(); + test_throwing_division(); + + test_valid_division(); + test_throwing_division(); + + return boost::report_errors(); +} diff --git a/test/test_unsigned_overflowing_mod.cpp b/test/test_unsigned_overflowing_mod.cpp new file mode 100644 index 0000000..def266f --- /dev/null +++ b/test/test_unsigned_overflowing_mod.cpp @@ -0,0 +1,150 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wold-style-cast" +# pragma clang diagnostic ignored "-Wundef" +# pragma clang diagnostic ignored "-Wconversion" +# pragma clang diagnostic ignored "-Wsign-conversion" +# pragma clang diagnostic ignored "-Wfloat-equal" +# pragma clang diagnostic ignored "-Wsign-compare" +# pragma clang diagnostic ignored "-Woverflow" + +# if (__clang_major__ >= 10 && !defined(__APPLE__)) || __clang_major__ >= 13 +# pragma clang diagnostic ignored "-Wdeprecated-copy" +# endif + +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wold-style-cast" +# pragma GCC diagnostic ignored "-Wundef" +# pragma GCC diagnostic ignored "-Wconversion" +# pragma GCC diagnostic ignored "-Wsign-conversion" +# pragma GCC diagnostic ignored "-Wsign-compare" +# pragma GCC diagnostic ignored "-Wfloat-equal" +# pragma GCC diagnostic ignored "-Woverflow" + +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4389) +# pragma warning(disable : 4127) +# pragma warning(disable : 4305) +# pragma warning(disable : 4309) +#endif + +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_COMPARE +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_CONVERSION + +#include + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +// Ignore [[nodiscard]] on the test that we know are going to throw +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-result" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-result" +#elif defined(_MSC_VER) +# pragma warning (push) +# pragma warning (disable: 4834) +#endif + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include +#include +#include +#include +#include + +#endif + +using namespace boost::safe_numbers; + +inline std::mt19937_64 rng{42}; +inline constexpr std::size_t N {1024}; + +template +void test_valid_division() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {1, std::numeric_limits::max()}; + + for (std::size_t i = 0; i < N; ++i) + { + const auto lhs_value {dist(rng)}; + const auto rhs_value {dist(rng)}; + + T ref_value {}; + if constexpr (std::is_same_v || std::is_same_v) + { + ref_value = static_cast(static_cast(static_cast(lhs_value % rhs_value))); + } + else + { + ref_value = static_cast(lhs_value % rhs_value); + } + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto [res, overflowed] = overflowing_mod(lhs, rhs); + + BOOST_TEST_EQ(ref_value, res); + } +} + +template +void test_throwing_division() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {1, std::numeric_limits::max()}; + + for (std::size_t i = 0; i < N; ++i) + { + const auto lhs_value {dist(rng)}; + constexpr basis_type rhs_value {0U}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + + BOOST_TEST_THROWS(overflowing_mod(lhs, rhs), std::domain_error); + } +} + +int main() +{ + test_valid_division(); + test_throwing_division(); + + test_valid_division(); + test_throwing_division(); + + test_valid_division(); + test_throwing_division(); + + test_valid_division(); + test_throwing_division(); + + test_valid_division(); + test_throwing_division(); + + return boost::report_errors(); +} diff --git a/test/test_unsigned_overflowing_multiplication.cpp b/test/test_unsigned_overflowing_multiplication.cpp new file mode 100644 index 0000000..5d739c6 --- /dev/null +++ b/test/test_unsigned_overflowing_multiplication.cpp @@ -0,0 +1,161 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wold-style-cast" +# pragma clang diagnostic ignored "-Wundef" +# pragma clang diagnostic ignored "-Wconversion" +# pragma clang diagnostic ignored "-Wsign-conversion" +# pragma clang diagnostic ignored "-Wfloat-equal" +# pragma clang diagnostic ignored "-Wsign-compare" +# pragma clang diagnostic ignored "-Woverflow" + +# if (__clang_major__ >= 10 && !defined(__APPLE__)) || __clang_major__ >= 13 +# pragma clang diagnostic ignored "-Wdeprecated-copy" +# endif + +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wold-style-cast" +# pragma GCC diagnostic ignored "-Wundef" +# pragma GCC diagnostic ignored "-Wconversion" +# pragma GCC diagnostic ignored "-Wsign-conversion" +# pragma GCC diagnostic ignored "-Wsign-compare" +# pragma GCC diagnostic ignored "-Wfloat-equal" +# pragma GCC diagnostic ignored "-Woverflow" + +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4389) +# pragma warning(disable : 4127) +# pragma warning(disable : 4305) +# pragma warning(disable : 4309) +#endif + +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_COMPARE +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_CONVERSION + +#include + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +// Ignore [[nodiscard]] on the test that we know are going to throw +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-result" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-result" +#elif defined(_MSC_VER) +# pragma warning (push) +# pragma warning (disable: 4834) +#endif + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include +#include +#include +#include +#include + +#endif + +using namespace boost::safe_numbers; + +inline std::mt19937_64 rng{42}; +inline constexpr std::size_t N {1024}; + +template +void test_valid_multiplication() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {0, sizeof(basis_type) * 8U - 1U}; + + for (std::size_t i = 0; i < N; ++i) + { + const auto lhs_value {dist(rng)}; + const auto rhs_value {dist(rng)}; + + T ref_value {}; + if constexpr (std::is_same_v || std::is_same_v) + { + ref_value = static_cast(static_cast(static_cast(lhs_value * rhs_value))); + } + else + { + ref_value = static_cast(lhs_value * rhs_value); + } + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto [res, overflowed] = overflowing_mul(lhs, rhs); + + BOOST_TEST_EQ(ref_value, res); + BOOST_TEST(!overflowed); + } +} + +template +void test_overflowing_multiplication() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {2U, std::numeric_limits::max()}; + + for (std::size_t i = 0; i < N; ++i) + { + constexpr basis_type lhs_value {std::numeric_limits::max() - 1U}; + const auto rhs_value {dist(rng)}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto [res, overflowed] = overflowing_mul(lhs, rhs); + + if constexpr (std::is_same_v || std::is_same_v) + { + BOOST_TEST_EQ(res, T{static_cast(static_cast(lhs_value) * static_cast(rhs_value))}); + } + else + { + BOOST_TEST_EQ(res, T{static_cast(lhs_value * rhs_value)}); + } + + BOOST_TEST(overflowed); + } +} + +int main() +{ + test_valid_multiplication(); + test_overflowing_multiplication(); + + test_valid_multiplication(); + test_overflowing_multiplication(); + + test_valid_multiplication(); + test_overflowing_multiplication(); + + test_valid_multiplication(); + test_overflowing_multiplication(); + + test_valid_multiplication(); + test_overflowing_multiplication(); + + return boost::report_errors(); +} diff --git a/test/test_unsigned_overflowing_subtraction.cpp b/test/test_unsigned_overflowing_subtraction.cpp new file mode 100644 index 0000000..cb573b2 --- /dev/null +++ b/test/test_unsigned_overflowing_subtraction.cpp @@ -0,0 +1,158 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wold-style-cast" +# pragma clang diagnostic ignored "-Wundef" +# pragma clang diagnostic ignored "-Wconversion" +# pragma clang diagnostic ignored "-Wsign-conversion" +# pragma clang diagnostic ignored "-Wfloat-equal" +# pragma clang diagnostic ignored "-Wsign-compare" +# pragma clang diagnostic ignored "-Woverflow" + +# if (__clang_major__ >= 10 && !defined(__APPLE__)) || __clang_major__ >= 13 +# pragma clang diagnostic ignored "-Wdeprecated-copy" +# endif + +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wold-style-cast" +# pragma GCC diagnostic ignored "-Wundef" +# pragma GCC diagnostic ignored "-Wconversion" +# pragma GCC diagnostic ignored "-Wsign-conversion" +# pragma GCC diagnostic ignored "-Wsign-compare" +# pragma GCC diagnostic ignored "-Wfloat-equal" +# pragma GCC diagnostic ignored "-Woverflow" + +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4389) +# pragma warning(disable : 4127) +# pragma warning(disable : 4305) +# pragma warning(disable : 4309) +#endif + +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_COMPARE +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_CONVERSION + +#include + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +// Ignore [[nodiscard]] on the test that we know are going to throw +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-result" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-result" +#elif defined(_MSC_VER) +# pragma warning (push) +# pragma warning (disable: 4834) +#endif + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include +#include +#include +#include +#include + +#endif + +using namespace boost::safe_numbers; + +inline std::mt19937_64 rng{42}; +inline constexpr std::size_t N {1024}; + +template +void test_valid_subtraction() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {std::numeric_limits::min() / 2U, + std::numeric_limits::max() / 2U}; + + for (std::size_t i = 0; i < N; ++i) + { + auto lhs_value {dist(rng)}; + auto rhs_value {dist(rng)}; + + if (lhs_value < rhs_value) + { + std::swap(lhs_value, rhs_value); + } + + T ref_value {}; + if constexpr (std::is_same_v || std::is_same_v) + { + ref_value = static_cast(static_cast(static_cast(lhs_value - rhs_value))); + } + else + { + ref_value = static_cast(lhs_value - rhs_value); + } + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto [res, overflowed] = overflowing_sub(lhs, rhs); + + BOOST_TEST_EQ(ref_value, res); + BOOST_TEST(!overflowed); + } +} + +template +void test_saturating_subtraction() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {1U, std::numeric_limits::max() - 1U}; + + for (std::size_t i = 0; i < N; ++i) + { + const auto lhs_value {dist(rng)}; + constexpr basis_type rhs_value {std::numeric_limits::max()}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + + const auto [res, overflowed] = overflowing_sub(lhs, rhs); + + BOOST_TEST_EQ(res, T{static_cast(lhs_value - std::numeric_limits::max())}); + BOOST_TEST(overflowed); + } +} + + +int main() +{ + test_valid_subtraction(); + test_valid_subtraction(); + test_valid_subtraction(); + test_valid_subtraction(); + test_valid_subtraction(); + + test_saturating_subtraction(); + test_saturating_subtraction(); + test_saturating_subtraction(); + test_saturating_subtraction(); + test_saturating_subtraction(); + + return boost::report_errors(); +} diff --git a/test/test_unsigned_saturating_addition.cpp b/test/test_unsigned_saturating_addition.cpp index 0b09afb..e500d12 100644 --- a/test/test_unsigned_saturating_addition.cpp +++ b/test/test_unsigned_saturating_addition.cpp @@ -105,7 +105,7 @@ void test_valid_addition() const T lhs {lhs_value}; const T rhs {rhs_value}; - const T res {add_sat(lhs, rhs)}; + const T res {saturating_add(lhs, rhs)}; BOOST_TEST_EQ(ref_value, res); } @@ -125,7 +125,7 @@ void test_saturated_addition() const T lhs {lhs_value}; const T rhs {rhs_value}; - const T res {add_sat(lhs, rhs)}; + const T res {saturating_add(lhs, rhs)}; BOOST_TEST_EQ(res, max_value); } diff --git a/test/test_unsigned_saturating_division.cpp b/test/test_unsigned_saturating_division.cpp index 109447a..ca183df 100644 --- a/test/test_unsigned_saturating_division.cpp +++ b/test/test_unsigned_saturating_division.cpp @@ -105,7 +105,7 @@ void test_valid_division() const T lhs {lhs_value}; const T rhs {rhs_value}; - const T res {div_sat(lhs, rhs)}; + const T res {saturating_div(lhs, rhs)}; BOOST_TEST_EQ(ref_value, res); } @@ -125,7 +125,7 @@ void test_throwing_division() const T lhs {lhs_value}; const T rhs {rhs_value}; - BOOST_TEST_EQ(div_sat(lhs, rhs), std::numeric_limits::max()); + BOOST_TEST_THROWS(saturating_div(lhs, rhs), std::domain_error); } } diff --git a/test/test_unsigned_saturating_mod.cpp b/test/test_unsigned_saturating_mod.cpp new file mode 100644 index 0000000..67211a6 --- /dev/null +++ b/test/test_unsigned_saturating_mod.cpp @@ -0,0 +1,150 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wold-style-cast" +# pragma clang diagnostic ignored "-Wundef" +# pragma clang diagnostic ignored "-Wconversion" +# pragma clang diagnostic ignored "-Wsign-conversion" +# pragma clang diagnostic ignored "-Wfloat-equal" +# pragma clang diagnostic ignored "-Wsign-compare" +# pragma clang diagnostic ignored "-Woverflow" + +# if (__clang_major__ >= 10 && !defined(__APPLE__)) || __clang_major__ >= 13 +# pragma clang diagnostic ignored "-Wdeprecated-copy" +# endif + +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wold-style-cast" +# pragma GCC diagnostic ignored "-Wundef" +# pragma GCC diagnostic ignored "-Wconversion" +# pragma GCC diagnostic ignored "-Wsign-conversion" +# pragma GCC diagnostic ignored "-Wsign-compare" +# pragma GCC diagnostic ignored "-Wfloat-equal" +# pragma GCC diagnostic ignored "-Woverflow" + +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4389) +# pragma warning(disable : 4127) +# pragma warning(disable : 4305) +# pragma warning(disable : 4309) +#endif + +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_COMPARE +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_CONVERSION + +#include + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +// Ignore [[nodiscard]] on the test that we know are going to throw +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-result" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-result" +#elif defined(_MSC_VER) +# pragma warning (push) +# pragma warning (disable: 4834) +#endif + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include +#include +#include +#include +#include + +#endif + +using namespace boost::safe_numbers; + +inline std::mt19937_64 rng{42}; +inline constexpr std::size_t N {1024}; + +template +void test_valid_division() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {1, std::numeric_limits::max()}; + + for (std::size_t i = 0; i < N; ++i) + { + const auto lhs_value {dist(rng)}; + const auto rhs_value {dist(rng)}; + + T ref_value {}; + if constexpr (std::is_same_v || std::is_same_v) + { + ref_value = static_cast(static_cast(static_cast(lhs_value % rhs_value))); + } + else + { + ref_value = static_cast(lhs_value % rhs_value); + } + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const T res {saturating_mod(lhs, rhs)}; + + BOOST_TEST_EQ(ref_value, res); + } +} + +template +void test_throwing_division() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {1, std::numeric_limits::max()}; + + for (std::size_t i = 0; i < N; ++i) + { + const auto lhs_value {dist(rng)}; + constexpr basis_type rhs_value {0U}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + + BOOST_TEST_THROWS(saturating_mod(lhs, rhs), std::domain_error); + } +} + +int main() +{ + test_valid_division(); + test_throwing_division(); + + test_valid_division(); + test_throwing_division(); + + test_valid_division(); + test_throwing_division(); + + test_valid_division(); + test_throwing_division(); + + test_valid_division(); + test_throwing_division(); + + return boost::report_errors(); +} diff --git a/test/test_unsigned_saturating_multiplication.cpp b/test/test_unsigned_saturating_multiplication.cpp index 382d2a7..02bcbde 100644 --- a/test/test_unsigned_saturating_multiplication.cpp +++ b/test/test_unsigned_saturating_multiplication.cpp @@ -105,14 +105,14 @@ void test_valid_multiplication() const T lhs {lhs_value}; const T rhs {rhs_value}; - const T res {mul_sat(lhs, rhs)}; + const T res {saturating_mul(lhs, rhs)}; BOOST_TEST(ref_value == res); } } template -void test_throwing_multiplication() +void test_saturating_multiplication() { using basis_type = detail::underlying_type_t; boost::random::uniform_int_distribution dist {2U, std::numeric_limits::max()}; @@ -125,26 +125,26 @@ void test_throwing_multiplication() const T lhs {lhs_value}; const T rhs {rhs_value}; - BOOST_TEST_EQ(mul_sat(lhs, rhs), std::numeric_limits::max()); + BOOST_TEST_EQ(saturating_mul(lhs, rhs), std::numeric_limits::max()); } } int main() { test_valid_multiplication(); - test_throwing_multiplication(); + test_saturating_multiplication(); test_valid_multiplication(); - test_throwing_multiplication(); + test_saturating_multiplication(); test_valid_multiplication(); - test_throwing_multiplication(); + test_saturating_multiplication(); test_valid_multiplication(); - test_throwing_multiplication(); + test_saturating_multiplication(); test_valid_multiplication(); - test_throwing_multiplication(); + test_saturating_multiplication(); return boost::report_errors(); } diff --git a/test/test_unsigned_saturating_subtraction.cpp b/test/test_unsigned_saturating_subtraction.cpp index 4f40f34..7e5fb47 100644 --- a/test/test_unsigned_saturating_subtraction.cpp +++ b/test/test_unsigned_saturating_subtraction.cpp @@ -111,7 +111,7 @@ void test_valid_subtraction() const T lhs {lhs_value}; const T rhs {rhs_value}; - const T res {sub_sat(lhs, rhs)}; + const T res {saturating_sub(lhs, rhs)}; BOOST_TEST(ref_value == res); } @@ -131,7 +131,7 @@ void test_saturating_subtraction() const T lhs {lhs_value}; const T rhs {rhs_value}; - BOOST_TEST_EQ(sub_sat(lhs, rhs), std::numeric_limits::min()); + BOOST_TEST_EQ(saturating_sub(lhs, rhs), std::numeric_limits::min()); } }