diff --git a/doc/modules/ROOT/pages/policies.adoc b/doc/modules/ROOT/pages/policies.adoc index 81894d2..a47fb2a 100644 --- a/doc/modules/ROOT/pages/policies.adoc +++ b/doc/modules/ROOT/pages/policies.adoc @@ -29,6 +29,7 @@ enum class overflow_policy checked, // Return std::nullopt on overflow/underflow wrapping, // Wrap silently strict, // Call std::exit(EXIT_FAILURE) on error + widen, // Promote to the next wider type (add/mul only) }; } // namespace boost::safe_numbers @@ -68,6 +69,11 @@ enum class overflow_policy | Calls `std::exit(EXIT_FAILURE)` | Calls `std::exit(EXIT_FAILURE)` | Yes + +| `widen` +| Promotes to next wider type (add/mul only) +| N/A (only add/mul supported) +| Yes |=== == Named Arithmetic Functions @@ -217,6 +223,28 @@ These functions call `std::exit(EXIT_FAILURE)` on error, providing a hard termin All strict functions are marked `noexcept` since `std::exit` does not throw. +=== Widening Arithmetic + +[source,c++] +---- +template +constexpr auto widening_add(T lhs, T rhs) noexcept; + +template +constexpr auto widening_mul(T lhs, T rhs) noexcept; +---- + +These functions avoid overflow entirely by promoting the result to the next wider unsigned integer type. +The promotion chain is: `uint8` -> `u16`, `u16` -> `u32`, `u32` -> `u64`, `u64` -> `uint128`. +Since `uint128` is the widest supported type, widening is not available for `uint128` operands (a `static_assert` fires). + +Only addition and multiplication are provided because subtraction, division, and modulo cannot overflow into a range that requires a wider type. + +- `widening_add`: Returns the sum in the next wider type +- `widening_mul`: Returns the product in the next wider type + +Both functions are `noexcept`. + == Generic Policy-Parameterized Arithmetic [source,c++] @@ -260,6 +288,9 @@ The return type depends on the policy: | `overflow_policy::strict` | `T` + +| `overflow_policy::widen` +| Next wider unsigned integer type (add/mul only) |=== This allows writing generic code parameterized on the overflow policy: diff --git a/include/boost/safe_numbers/detail/type_traits.hpp b/include/boost/safe_numbers/detail/type_traits.hpp index 210dce1..389242b 100644 --- a/include/boost/safe_numbers/detail/type_traits.hpp +++ b/include/boost/safe_numbers/detail/type_traits.hpp @@ -55,6 +55,14 @@ struct underlying> } // namespace impl +// Promotes an unsigned integer to the next higher type +// uint128_t becomes bool so that we can static_assert on bool check that we can't widen uint128_t +template +using promoted_type = std::conditional_t, std::uint16_t, + std::conditional_t, std::uint32_t, + std::conditional_t, std::uint64_t, + std::conditional_t, int128::uint128_t, bool>>>>; + } // namespace boost::safe_numbers::detail #endif // BOOST_SAFE_NUMBERS_DETAIL_TYPE_TRAITS_HPP diff --git a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp index 4555f9c..0ebc47a 100644 --- a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp +++ b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp @@ -7,6 +7,7 @@ #include #include +#include #include #ifndef BOOST_SAFE_NUMBERS_BUILD_MODULE @@ -344,10 +345,25 @@ struct add_helper } }; +// Partial specialization for widening policy +template +struct add_helper +{ + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + { + using promoted_type = promoted_type; + static_assert(!std::is_same_v, "Widening policy with uint128_t is not supported"); + + using result_type = unsigned_integer_basis; + return result_type{static_cast(static_cast(lhs) + rhs)}; + } +}; + 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 || Policy == overflow_policy::checked || Policy == overflow_policy::wrapping || Policy == overflow_policy::strict) + noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || Policy == overflow_policy::checked || Policy == overflow_policy::wrapping || Policy == overflow_policy::strict || Policy == overflow_policy::widen) { return add_helper::apply(lhs, rhs); } @@ -981,10 +997,25 @@ struct mul_helper } }; +// Partial specialization for widening policy +template +struct mul_helper +{ + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + { + using promoted_type = promoted_type; + static_assert(!std::is_same_v, "Widening policy with uint128_t is not supported"); + + using result_type = unsigned_integer_basis; + return result_type{static_cast(static_cast(lhs) * rhs)}; + } +}; + 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 || Policy == overflow_policy::checked || Policy == overflow_policy::wrapping || Policy == overflow_policy::strict) + noexcept(Policy == overflow_policy::saturate || Policy == overflow_policy::overflow_tuple || Policy == overflow_policy::checked || Policy == overflow_policy::wrapping || Policy == overflow_policy::strict || Policy == overflow_policy::widen) { return mul_helper::apply(lhs, rhs); } @@ -1637,6 +1668,24 @@ template BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("strict modulo", strict_mod) +template +[[nodiscard]] constexpr auto widening_add(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept +{ + return detail::add_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("widening add", widening_add) + +template +[[nodiscard]] constexpr auto widening_mul(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept +{ + return detail::mul_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("widening mul", widening_mul) + // ------------------------------ // Generic policy-parameterized functions // ------------------------------ @@ -1670,6 +1719,14 @@ template { 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 @@ -1701,6 +1758,10 @@ template { return strict_sub(lhs, rhs); } + else + { + static_assert(detail::dependent_false, "Policy is not supported for subtraction"); + } } template @@ -1732,6 +1793,14 @@ template { 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 @@ -1763,6 +1832,10 @@ template { return strict_div(lhs, rhs); } + else + { + static_assert(detail::dependent_false, "Policy is not supported for division"); + } } template @@ -1794,6 +1867,10 @@ template { return strict_mod(lhs, rhs); } + else + { + static_assert(detail::dependent_false, "Policy is not supported for modulo"); + } } } // namespace boost::safe_numbers diff --git a/include/boost/safe_numbers/overflow_policy.hpp b/include/boost/safe_numbers/overflow_policy.hpp index 7a3dd32..09c15cc 100644 --- a/include/boost/safe_numbers/overflow_policy.hpp +++ b/include/boost/safe_numbers/overflow_policy.hpp @@ -15,6 +15,7 @@ enum class overflow_policy checked, wrapping, strict, + widen, }; } // namespace boost::safe_numbers