diff --git a/doc/modules/ROOT/pages/charconv.adoc b/doc/modules/ROOT/pages/charconv.adoc index 3638ef0..b019da0 100644 --- a/doc/modules/ROOT/pages/charconv.adoc +++ b/doc/modules/ROOT/pages/charconv.adoc @@ -10,8 +10,6 @@ https://www.boost.org/LICENSE_1_0.txt The following functions provide character conversion support for safe integer types using https://www.boost.org/doc/libs/master/libs/charconv/doc/html/charconv.html[Boost.Charconv]. -IMPORTANT: Using this header requires Boost.Charconv headers to be present. - [#to_chars] == to_chars diff --git a/include/boost/safe_numbers/detail/overflow_policy.hpp b/include/boost/safe_numbers/detail/overflow_policy.hpp index 185127c..614d7c6 100644 --- a/include/boost/safe_numbers/detail/overflow_policy.hpp +++ b/include/boost/safe_numbers/detail/overflow_policy.hpp @@ -12,6 +12,7 @@ enum class overflow_policy throw_exception, saturate, overflow_tuple, + checked, }; } // namespace boost::safe_numbers::detail diff --git a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp index eab8458..0609452 100644 --- a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp +++ b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp @@ -18,6 +18,7 @@ #include #include #include +#include #endif // BOOST_SAFE_NUMBERS_BUILD_MODULE @@ -268,10 +269,42 @@ struct add_helper } }; +// Partial specialization for overflow_tuple policy +template +struct add_helper +{ + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> std::optional> + { + using result_type = unsigned_integer_basis; + + const auto lhs_basis {static_cast(lhs)}; + const auto rhs_basis {static_cast(rhs)}; + BasisType res {}; + + if constexpr (!std::is_same_v) + { + #if BOOST_SAFE_NUMBERS_HAS_BUILTIN(__builtin_add_overflow) || BOOST_SAFE_NUMBERS_HAS_BUILTIN(_addcarry_u64) || defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X86_INTRIN) + + if (!std::is_constant_evaluated()) + { + const auto overflowed {impl::unsigned_intrin_add(lhs_basis, rhs_basis, res)}; + return overflowed ? std::nullopt : std::make_optional(result_type{res}); + } + + #endif + } + + const auto overflowed {impl::unsigned_no_intrin_add(lhs_basis, rhs_basis, res)}; + return overflowed ? std::nullopt : std::make_optional(result_type{res}); + } +}; + template [[nodiscard]] constexpr auto add_impl(const unsigned_integer_basis lhs, const unsigned_integer_basis rhs) - noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple) + noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || Policy == overflow_policy::checked) { return add_helper::apply(lhs, rhs); } @@ -567,10 +600,42 @@ struct sub_helper } }; +// Partial specialization for checked policy +template +struct sub_helper +{ + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> std::optional> + { + using result_type = unsigned_integer_basis; + + const auto lhs_basis {static_cast(lhs)}; + const auto rhs_basis {static_cast(rhs)}; + BasisType res {}; + + if constexpr (!std::is_same_v) + { + #if BOOST_SAFE_NUMBERS_HAS_BUILTIN(__builtin_sub_overflow) || BOOST_SAFE_NUMBERS_HAS_BUILTIN(_subborrow_u64) || defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X86_INTRIN) + + if (!std::is_constant_evaluated()) + { + const auto underflowed {impl::unsigned_intrin_sub(lhs_basis, rhs_basis, res)}; + return underflowed ? std::nullopt : std::make_optional(result_type{res}); + } + + #endif + } + + const auto underflowed {impl::unsigned_no_intrin_sub(lhs_basis, rhs_basis, res)}; + return underflowed ? std::nullopt : std::make_optional(result_type{res}); + } +}; + template [[nodiscard]] constexpr auto sub_impl(const unsigned_integer_basis lhs, const unsigned_integer_basis rhs) - noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple) + noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || Policy == overflow_policy::checked) { return sub_helper::apply(lhs, rhs); } @@ -767,10 +832,42 @@ struct mul_helper } }; +// Partial specialization for checked policy +template +struct mul_helper +{ + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> std::optional> + { + using result_type = unsigned_integer_basis; + + const auto lhs_basis {static_cast(lhs)}; + const auto rhs_basis {static_cast(rhs)}; + BasisType res {}; + + if constexpr (!std::is_same_v) + { + #if BOOST_SAFE_NUMBERS_HAS_BUILTIN(__builtin_mul_overflow) || BOOST_SAFE_NUMBERS_HAS_BUILTIN(_umul128) + + if (!std::is_constant_evaluated()) + { + const auto overflowed {impl::unsigned_intrin_mul(lhs_basis, rhs_basis, res)}; + return overflowed ? std::nullopt : std::make_optional(result_type{res}); + } + + #endif + } + + const auto overflowed {impl::no_intrin_mul(lhs_basis, rhs_basis, res)}; + return overflowed ? std::nullopt : std::make_optional(result_type{res}); + } +}; + template [[nodiscard]] constexpr auto mul_impl(const unsigned_integer_basis lhs, const unsigned_integer_basis rhs) - noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple) + noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || Policy == overflow_policy::checked) { return mul_helper::apply(lhs, rhs); } @@ -861,9 +958,37 @@ struct div_helper } }; +// Partial specialization for checked policy +template +struct div_helper +{ + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> std::optional> + { + using result_type = unsigned_integer_basis; + + const auto divisor {static_cast(rhs)}; + if (divisor == 0U) [[unlikely]] + { + return std::nullopt; + } + + if constexpr (std::is_same_v || std::is_same_v) + { + return std::make_optional(result_type{static_cast(static_cast(lhs) / divisor)}); + } + else + { + return std::make_optional(result_type{static_cast(lhs) / divisor}); + } + } +}; + template [[nodiscard]] constexpr auto div_impl(const unsigned_integer_basis lhs, const unsigned_integer_basis rhs) + noexcept(Policy == overflow_policy::checked) { return div_helper::apply(lhs, rhs); } @@ -954,9 +1079,37 @@ struct mod_helper } }; +// Partial specialization for checked policy +template +struct mod_helper +{ + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> std::optional> + { + using result_type = unsigned_integer_basis; + + const auto divisor {static_cast(rhs)}; + if (divisor == 0U) [[unlikely]] + { + return std::nullopt; + } + + if constexpr (std::is_same_v || std::is_same_v) + { + return std::make_optional(result_type{static_cast(static_cast(lhs) % divisor)}); + } + else + { + return std::make_optional(result_type{static_cast(lhs) % divisor}); + } + } +}; + template [[nodiscard]] constexpr auto mod_impl(const unsigned_integer_basis lhs, const unsigned_integer_basis rhs) + noexcept(Policy == overflow_policy::checked) { return mod_helper::apply(lhs, rhs); } @@ -1107,6 +1260,8 @@ template return detail::add_impl(lhs, rhs); } +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("overflowing addition", overflowing_add) + template [[nodiscard]] constexpr auto overflowing_sub(const detail::unsigned_integer_basis lhs, const detail::unsigned_integer_basis rhs) noexcept @@ -1115,6 +1270,8 @@ template return detail::sub_impl(lhs, rhs); } +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("overflowing subtraction", overflowing_sub) + template [[nodiscard]] constexpr auto overflowing_mul(const detail::unsigned_integer_basis lhs, const detail::unsigned_integer_basis rhs) noexcept @@ -1123,6 +1280,8 @@ template return detail::mul_impl(lhs, rhs); } +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("overflowing multiplication", overflowing_mul) + template [[nodiscard]] constexpr auto overflowing_div(const detail::unsigned_integer_basis lhs, const detail::unsigned_integer_basis rhs) @@ -1131,6 +1290,8 @@ template return detail::div_impl(lhs, rhs); } +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("overflowing division", overflowing_div) + template [[nodiscard]] constexpr auto overflowing_mod(const detail::unsigned_integer_basis lhs, const detail::unsigned_integer_basis rhs) @@ -1139,6 +1300,58 @@ template return detail::mod_impl(lhs, rhs); } +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("overflowing modulo", overflowing_mod) + +template +[[nodiscard]] constexpr auto checked_add(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::optional> +{ + return detail::add_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("checked addition", checked_add) + +template +[[nodiscard]] constexpr auto checked_sub(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::optional> +{ + return detail::sub_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("checked subtraction", checked_sub) + +template +[[nodiscard]] constexpr auto checked_mul(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::optional> +{ + return detail::mul_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("checked multiplication", checked_mul) + +template +[[nodiscard]] constexpr auto checked_div(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::optional> +{ + return detail::div_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("checked division", checked_div) + +template +[[nodiscard]] constexpr auto checked_mod(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::optional> +{ + return detail::mod_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("checked modulo", checked_mod) + } // namespace boost::safe_numbers #undef BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP diff --git a/test/Jamfile b/test/Jamfile index da32648..a1e15c6 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -80,6 +80,11 @@ run test_unsigned_overflowing_subtraction.cpp ; run test_unsigned_overflowing_multiplication.cpp ; run test_unsigned_overflowing_division.cpp ; run test_unsigned_overflowing_mod.cpp ; +run test_unsigned_checked_addition.cpp ; +run test_unsigned_checked_subtraction.cpp ; +run test_unsigned_checked_multiplication.cpp ; +run test_unsigned_checked_division.cpp ; +run test_unsigned_checked_mod.cpp ; # Compile Tests compile compile_tests/compile_test_unsigned_integers.cpp ; diff --git a/test/test_unsigned_checked_addition.cpp b/test/test_unsigned_checked_addition.cpp new file mode 100644 index 0000000..7a28c5f --- /dev/null +++ b/test/test_unsigned_checked_addition.cpp @@ -0,0 +1,152 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wold-style-cast" +# pragma clang diagnostic ignored "-Wundef" +# pragma clang diagnostic ignored "-Wconversion" +# pragma clang diagnostic ignored "-Wsign-conversion" +# pragma clang diagnostic ignored "-Wfloat-equal" +# pragma clang diagnostic ignored "-Wsign-compare" +# pragma clang diagnostic ignored "-Woverflow" + +# if (__clang_major__ >= 10 && !defined(__APPLE__)) || __clang_major__ >= 13 +# pragma clang diagnostic ignored "-Wdeprecated-copy" +# endif + +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wold-style-cast" +# pragma GCC diagnostic ignored "-Wundef" +# pragma GCC diagnostic ignored "-Wconversion" +# pragma GCC diagnostic ignored "-Wsign-conversion" +# pragma GCC diagnostic ignored "-Wsign-compare" +# pragma GCC diagnostic ignored "-Wfloat-equal" +# pragma GCC diagnostic ignored "-Woverflow" + +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4389) +# pragma warning(disable : 4127) +# pragma warning(disable : 4305) +# pragma warning(disable : 4309) +#endif + +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_COMPARE +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_CONVERSION + +#include + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +// Ignore [[nodiscard]] on the test that we know are going to throw +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-result" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-result" +#elif defined(_MSC_VER) +# pragma warning (push) +# pragma warning (disable: 4834) +#endif + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include +#include +#include +#include + +#endif + +using namespace boost::safe_numbers; + +inline std::mt19937_64 rng{42}; +inline constexpr std::size_t N {1024}; + +template +void test_valid_addition() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {std::numeric_limits::min() / 2U, + std::numeric_limits::max() / 2U}; + + for (std::size_t i = 0; i < N; ++i) + { + const auto lhs_value {dist(rng)}; + const auto rhs_value {dist(rng)}; + + T ref_value {}; + if constexpr (std::is_same_v || std::is_same_v) + { + ref_value = static_cast(static_cast(static_cast(lhs_value + rhs_value))); + } + else + { + ref_value = static_cast(lhs_value + rhs_value); + } + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto res {checked_add(lhs, rhs)}; + + BOOST_TEST(res.has_value()); + BOOST_TEST_EQ(ref_value, res.value()); + } +} + +template +void test_checked_addition() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {2U, std::numeric_limits::max()}; + + for (std::size_t i = 0; i < N; ++i) + { + constexpr basis_type lhs_value {std::numeric_limits::max() - 1U}; + const auto rhs_value {dist(rng)}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto res {checked_add(lhs, rhs)}; + + BOOST_TEST(!res.has_value()); + } +} + +int main() +{ + test_valid_addition(); + test_checked_addition(); + + test_valid_addition(); + test_checked_addition(); + + test_valid_addition(); + test_checked_addition(); + + test_valid_addition(); + test_checked_addition(); + + test_valid_addition(); + test_checked_addition(); + + return boost::report_errors(); +} diff --git a/test/test_unsigned_checked_division.cpp b/test/test_unsigned_checked_division.cpp new file mode 100644 index 0000000..760a46e --- /dev/null +++ b/test/test_unsigned_checked_division.cpp @@ -0,0 +1,152 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wold-style-cast" +# pragma clang diagnostic ignored "-Wundef" +# pragma clang diagnostic ignored "-Wconversion" +# pragma clang diagnostic ignored "-Wsign-conversion" +# pragma clang diagnostic ignored "-Wfloat-equal" +# pragma clang diagnostic ignored "-Wsign-compare" +# pragma clang diagnostic ignored "-Woverflow" + +# if (__clang_major__ >= 10 && !defined(__APPLE__)) || __clang_major__ >= 13 +# pragma clang diagnostic ignored "-Wdeprecated-copy" +# endif + +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wold-style-cast" +# pragma GCC diagnostic ignored "-Wundef" +# pragma GCC diagnostic ignored "-Wconversion" +# pragma GCC diagnostic ignored "-Wsign-conversion" +# pragma GCC diagnostic ignored "-Wsign-compare" +# pragma GCC diagnostic ignored "-Wfloat-equal" +# pragma GCC diagnostic ignored "-Woverflow" + +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4389) +# pragma warning(disable : 4127) +# pragma warning(disable : 4305) +# pragma warning(disable : 4309) +#endif + +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_COMPARE +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_CONVERSION + +#include + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +// Ignore [[nodiscard]] on the test that we know are going to throw +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-result" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-result" +#elif defined(_MSC_VER) +# pragma warning (push) +# pragma warning (disable: 4834) +#endif + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include +#include +#include +#include +#include + +#endif + +using namespace boost::safe_numbers; + +inline std::mt19937_64 rng{42}; +inline constexpr std::size_t N {1024}; + +template +void test_valid_division() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {1, std::numeric_limits::max()}; + + for (std::size_t i = 0; i < N; ++i) + { + const auto lhs_value {dist(rng)}; + const auto rhs_value {dist(rng)}; + + T ref_value {}; + if constexpr (std::is_same_v || std::is_same_v) + { + ref_value = static_cast(static_cast(static_cast(lhs_value / rhs_value))); + } + else + { + ref_value = static_cast(lhs_value / rhs_value); + } + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto res {checked_div(lhs, rhs)}; + + BOOST_TEST(res.has_value()); + BOOST_TEST_EQ(ref_value, res.value()); + } +} + +template +void test_throwing_division() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {1, std::numeric_limits::max()}; + + for (std::size_t i = 0; i < N; ++i) + { + const auto lhs_value {dist(rng)}; + constexpr basis_type rhs_value {0U}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto res {checked_div(lhs, rhs)}; + + BOOST_TEST(!res.has_value()); + } +} + +int main() +{ + test_valid_division(); + test_throwing_division(); + + test_valid_division(); + test_throwing_division(); + + test_valid_division(); + test_throwing_division(); + + test_valid_division(); + test_throwing_division(); + + test_valid_division(); + test_throwing_division(); + + return boost::report_errors(); +} diff --git a/test/test_unsigned_checked_mod.cpp b/test/test_unsigned_checked_mod.cpp new file mode 100644 index 0000000..4624db7 --- /dev/null +++ b/test/test_unsigned_checked_mod.cpp @@ -0,0 +1,152 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wold-style-cast" +# pragma clang diagnostic ignored "-Wundef" +# pragma clang diagnostic ignored "-Wconversion" +# pragma clang diagnostic ignored "-Wsign-conversion" +# pragma clang diagnostic ignored "-Wfloat-equal" +# pragma clang diagnostic ignored "-Wsign-compare" +# pragma clang diagnostic ignored "-Woverflow" + +# if (__clang_major__ >= 10 && !defined(__APPLE__)) || __clang_major__ >= 13 +# pragma clang diagnostic ignored "-Wdeprecated-copy" +# endif + +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wold-style-cast" +# pragma GCC diagnostic ignored "-Wundef" +# pragma GCC diagnostic ignored "-Wconversion" +# pragma GCC diagnostic ignored "-Wsign-conversion" +# pragma GCC diagnostic ignored "-Wsign-compare" +# pragma GCC diagnostic ignored "-Wfloat-equal" +# pragma GCC diagnostic ignored "-Woverflow" + +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4389) +# pragma warning(disable : 4127) +# pragma warning(disable : 4305) +# pragma warning(disable : 4309) +#endif + +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_COMPARE +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_CONVERSION + +#include + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +// Ignore [[nodiscard]] on the test that we know are going to throw +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-result" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-result" +#elif defined(_MSC_VER) +# pragma warning (push) +# pragma warning (disable: 4834) +#endif + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include +#include +#include +#include +#include + +#endif + +using namespace boost::safe_numbers; + +inline std::mt19937_64 rng{42}; +inline constexpr std::size_t N {1024}; + +template +void test_valid_division() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {1, std::numeric_limits::max()}; + + for (std::size_t i = 0; i < N; ++i) + { + const auto lhs_value {dist(rng)}; + const auto rhs_value {dist(rng)}; + + T ref_value {}; + if constexpr (std::is_same_v || std::is_same_v) + { + ref_value = static_cast(static_cast(static_cast(lhs_value % rhs_value))); + } + else + { + ref_value = static_cast(lhs_value % rhs_value); + } + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto res {checked_mod(lhs, rhs)}; + + BOOST_TEST(res.has_value()); + BOOST_TEST_EQ(ref_value, res.value()); + } +} + +template +void test_throwing_division() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {1, std::numeric_limits::max()}; + + for (std::size_t i = 0; i < N; ++i) + { + const auto lhs_value {dist(rng)}; + constexpr basis_type rhs_value {0U}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto res {checked_mod(lhs, rhs)}; + + BOOST_TEST(!res.has_value()); + } +} + +int main() +{ + test_valid_division(); + test_throwing_division(); + + test_valid_division(); + test_throwing_division(); + + test_valid_division(); + test_throwing_division(); + + test_valid_division(); + test_throwing_division(); + + test_valid_division(); + test_throwing_division(); + + return boost::report_errors(); +} diff --git a/test/test_unsigned_checked_multiplication.cpp b/test/test_unsigned_checked_multiplication.cpp new file mode 100644 index 0000000..3137c7f --- /dev/null +++ b/test/test_unsigned_checked_multiplication.cpp @@ -0,0 +1,152 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wold-style-cast" +# pragma clang diagnostic ignored "-Wundef" +# pragma clang diagnostic ignored "-Wconversion" +# pragma clang diagnostic ignored "-Wsign-conversion" +# pragma clang diagnostic ignored "-Wfloat-equal" +# pragma clang diagnostic ignored "-Wsign-compare" +# pragma clang diagnostic ignored "-Woverflow" + +# if (__clang_major__ >= 10 && !defined(__APPLE__)) || __clang_major__ >= 13 +# pragma clang diagnostic ignored "-Wdeprecated-copy" +# endif + +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wold-style-cast" +# pragma GCC diagnostic ignored "-Wundef" +# pragma GCC diagnostic ignored "-Wconversion" +# pragma GCC diagnostic ignored "-Wsign-conversion" +# pragma GCC diagnostic ignored "-Wsign-compare" +# pragma GCC diagnostic ignored "-Wfloat-equal" +# pragma GCC diagnostic ignored "-Woverflow" + +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4389) +# pragma warning(disable : 4127) +# pragma warning(disable : 4305) +# pragma warning(disable : 4309) +#endif + +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_COMPARE +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_CONVERSION + +#include + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +// Ignore [[nodiscard]] on the test that we know are going to throw +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-result" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-result" +#elif defined(_MSC_VER) +# pragma warning (push) +# pragma warning (disable: 4834) +#endif + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include +#include +#include +#include +#include + +#endif + +using namespace boost::safe_numbers; + +inline std::mt19937_64 rng{42}; +inline constexpr std::size_t N {1024}; + +template +void test_valid_multiplication() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {0, sizeof(basis_type) * 8U - 1U}; + + for (std::size_t i = 0; i < N; ++i) + { + const auto lhs_value {dist(rng)}; + const auto rhs_value {dist(rng)}; + + T ref_value {}; + if constexpr (std::is_same_v || std::is_same_v) + { + ref_value = static_cast(static_cast(static_cast(lhs_value * rhs_value))); + } + else + { + ref_value = static_cast(lhs_value * rhs_value); + } + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto res {checked_mul(lhs, rhs)}; + + BOOST_TEST(res.has_value()); + BOOST_TEST_EQ(res.value(), ref_value); + } +} + +template +void test_checked_multiplication() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {2U, std::numeric_limits::max()}; + + for (std::size_t i = 0; i < N; ++i) + { + constexpr basis_type lhs_value {std::numeric_limits::max() - 1U}; + const auto rhs_value {dist(rng)}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto res {checked_mul(lhs, rhs)}; + + BOOST_TEST(!res.has_value()); + } +} + +int main() +{ + test_valid_multiplication(); + test_checked_multiplication(); + + test_valid_multiplication(); + test_checked_multiplication(); + + test_valid_multiplication(); + test_checked_multiplication(); + + test_valid_multiplication(); + test_checked_multiplication(); + + test_valid_multiplication(); + test_checked_multiplication(); + + return boost::report_errors(); +} diff --git a/test/test_unsigned_checked_subtraction.cpp b/test/test_unsigned_checked_subtraction.cpp new file mode 100644 index 0000000..0986f0c --- /dev/null +++ b/test/test_unsigned_checked_subtraction.cpp @@ -0,0 +1,156 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wold-style-cast" +# pragma clang diagnostic ignored "-Wundef" +# pragma clang diagnostic ignored "-Wconversion" +# pragma clang diagnostic ignored "-Wsign-conversion" +# pragma clang diagnostic ignored "-Wfloat-equal" +# pragma clang diagnostic ignored "-Wsign-compare" +# pragma clang diagnostic ignored "-Woverflow" + +# if (__clang_major__ >= 10 && !defined(__APPLE__)) || __clang_major__ >= 13 +# pragma clang diagnostic ignored "-Wdeprecated-copy" +# endif + +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wold-style-cast" +# pragma GCC diagnostic ignored "-Wundef" +# pragma GCC diagnostic ignored "-Wconversion" +# pragma GCC diagnostic ignored "-Wsign-conversion" +# pragma GCC diagnostic ignored "-Wsign-compare" +# pragma GCC diagnostic ignored "-Wfloat-equal" +# pragma GCC diagnostic ignored "-Woverflow" + +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4389) +# pragma warning(disable : 4127) +# pragma warning(disable : 4305) +# pragma warning(disable : 4309) +#endif + +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_COMPARE +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_CONVERSION + +#include + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +// Ignore [[nodiscard]] on the test that we know are going to throw +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-result" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-result" +#elif defined(_MSC_VER) +# pragma warning (push) +# pragma warning (disable: 4834) +#endif + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include +#include +#include +#include +#include + +#endif + +using namespace boost::safe_numbers; + +inline std::mt19937_64 rng{42}; +inline constexpr std::size_t N {1024}; + +template +void test_valid_subtraction() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {std::numeric_limits::min() / 2U, + std::numeric_limits::max() / 2U}; + + for (std::size_t i = 0; i < N; ++i) + { + auto lhs_value {dist(rng)}; + auto rhs_value {dist(rng)}; + + if (lhs_value < rhs_value) + { + std::swap(lhs_value, rhs_value); + } + + T ref_value {}; + if constexpr (std::is_same_v || std::is_same_v) + { + ref_value = static_cast(static_cast(static_cast(lhs_value - rhs_value))); + } + else + { + ref_value = static_cast(lhs_value - rhs_value); + } + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto res {checked_sub(lhs, rhs)}; + + BOOST_TEST(res.has_value()); + BOOST_TEST(ref_value == res); + } +} + +template +void test_checked_subtraction() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {1U, std::numeric_limits::max() - 1U}; + + for (std::size_t i = 0; i < N; ++i) + { + const auto lhs_value {dist(rng)}; + constexpr basis_type rhs_value {std::numeric_limits::max()}; + + const T lhs {lhs_value}; + const T rhs {rhs_value}; + const auto res {checked_sub(lhs, rhs)}; + + BOOST_TEST(!res.has_value()); + } +} + + +int main() +{ + test_valid_subtraction(); + test_valid_subtraction(); + test_valid_subtraction(); + test_valid_subtraction(); + test_valid_subtraction(); + + test_checked_subtraction(); + test_checked_subtraction(); + test_checked_subtraction(); + test_checked_subtraction(); + test_checked_subtraction(); + + return boost::report_errors(); +}