diff --git a/doc/modules/ROOT/pages/examples.adoc b/doc/modules/ROOT/pages/examples.adoc index 0d199b8..5f892c3 100644 --- a/doc/modules/ROOT/pages/examples.adoc +++ b/doc/modules/ROOT/pages/examples.adoc @@ -392,6 +392,25 @@ y >>= 4 -> 16 Left shift error: Left shift past the end of the type width Right shift error: Right shift past the end of the type width + +saturating_shl(u8(1), 4) = 16 +saturating_shl(u8(255), 1) = 255 +saturating_shr(u8(128), 4) = 8 +saturating_shr(u8(1), 8) = 0 + +overflowing_shl(u8(255), 1) = 254 (overflow: true) +overflowing_shr(u8(1), 8) = 0 (overflow: true) + +checked_shl(u8(1), 4) = 16 +checked_shl(u8(255), 1) = nullopt (overflow) +checked_shr(u8(1), 8) = nullopt (overflow) + +wrapping_shl(u8(255), 1) = 254 +wrapping_shr(u8(1), 8) = 0 + +shl(u32(1), 30) = 1073741824 +shl(u32(max), 1) = 4294967295 +shr(u32(8), 1) = 4 ---- ==== diff --git a/doc/modules/ROOT/pages/policies.adoc b/doc/modules/ROOT/pages/policies.adoc index a47fb2a..d54c1d1 100644 --- a/doc/modules/ROOT/pages/policies.adoc +++ b/doc/modules/ROOT/pages/policies.adoc @@ -10,8 +10,8 @@ https://www.boost.org/LICENSE_1_0.txt == Description -The library provides multiple overflow handling policies for arithmetic operations. -The default arithmetic operators (`+`, `-`, `*`, `/`, `%`) use the `throw_exception` policy, but alternative free functions and a generic policy-parameterized interface allow selecting different behavior at the call site. +The library provides multiple overflow handling policies for arithmetic and shift operations. +The default arithmetic operators (`+`, `-`, `*`, `/`, `%`) and shift operators (`<<`, `>>`) use the `throw_exception` policy, but alternative free functions and a generic policy-parameterized interface allow selecting different behavior at the call site. == The `overflow_policy` Enum @@ -245,6 +245,88 @@ Only addition and multiplication are provided because subtraction, division, and Both functions are `noexcept`. +== Named Shift Functions + +The same policy variants available for arithmetic operations are also available for shift operations. +The `widen` policy is not supported for shifts. + +=== Overflow Conditions + +- **Left shift (`<<`)**: Overflow occurs when `bit_width(lhs) + rhs >= std::numeric_limits::digits` (i.e., bits would be shifted past the type width). +- **Right shift (`>>`)**: Overflow occurs when `rhs >= std::numeric_limits::digits` (i.e., the shift amount is greater than or equal to the type width). + +=== Saturating Shifts + +[source,c++] +---- +template +constexpr T saturating_shl(T lhs, T rhs) noexcept; + +template +constexpr T saturating_shr(T lhs, T rhs) noexcept; +---- + +- `saturating_shl`: Returns the shifted value, saturating at `std::numeric_limits::max()` on overflow +- `saturating_shr`: Returns the shifted value, saturating at `0` when the shift amount is >= the type width + +=== Overflowing Shifts + +[source,c++] +---- +template +constexpr std::pair overflowing_shl(T lhs, T rhs) noexcept; + +template +constexpr std::pair overflowing_shr(T lhs, T rhs) noexcept; +---- + +- `overflowing_shl`: Returns the wrapped shifted value and `true` if overflow occurred +- `overflowing_shr`: Returns `0` and `true` if the shift amount is >= the type width; otherwise the shifted value and `false` + +=== Checked Shifts + +[source,c++] +---- +template +constexpr std::optional checked_shl(T lhs, T rhs) noexcept; + +template +constexpr std::optional checked_shr(T lhs, T rhs) noexcept; +---- + +- `checked_shl`: Returns the shifted value, or `std::nullopt` on overflow +- `checked_shr`: Returns the shifted value, or `std::nullopt` when the shift amount is >= the type width + +=== Wrapping Shifts + +[source,c++] +---- +template +constexpr T wrapping_shl(T lhs, T rhs) noexcept; + +template +constexpr T wrapping_shr(T lhs, T rhs) noexcept; +---- + +- `wrapping_shl`: Performs the shift, allowing bits to be lost without indication +- `wrapping_shr`: Returns `0` when the shift amount is >= the type width; otherwise performs the shift normally + +=== Strict Shifts + +[source,c++] +---- +template +constexpr T strict_shl(T lhs, T rhs) noexcept; + +template +constexpr T strict_shr(T lhs, T rhs) noexcept; +---- + +- `strict_shl`: Returns the shifted value; calls `std::exit(EXIT_FAILURE)` on overflow +- `strict_shr`: Returns the shifted value; calls `std::exit(EXIT_FAILURE)` when the shift amount is >= the type width + +All shift policy functions are `noexcept`. + == Generic Policy-Parameterized Arithmetic [source,c++] @@ -263,9 +345,16 @@ constexpr auto div(T lhs, T rhs); template constexpr auto mod(T lhs, T rhs); + +template +constexpr auto shl(T lhs, T rhs); + +template +constexpr auto shr(T lhs, T rhs); ---- These functions accept an `overflow_policy` as a template parameter and dispatch to the corresponding named function. +The `widen` policy is not supported for `shl` or `shr`. The return type depends on the policy: |=== @@ -345,6 +434,14 @@ The default operators and some named functions throw exceptions on error: | `std::underflow_error` | Value is zero +| `<<`, `<<=` +| `std::overflow_error` +| `bit_width(lhs) + rhs >= digits` + +| `>>`, `>>=` +| `std::overflow_error` +| `rhs >= digits` + | `saturating_div`, `saturating_mod` | `std::domain_error` | Division by zero diff --git a/doc/modules/ROOT/pages/unsigned_integers.adoc b/doc/modules/ROOT/pages/unsigned_integers.adoc index aeec9ae..1a10e60 100644 --- a/doc/modules/ROOT/pages/unsigned_integers.adoc +++ b/doc/modules/ROOT/pages/unsigned_integers.adoc @@ -225,6 +225,41 @@ constexpr T strict_div(T lhs, T rhs) noexcept; template constexpr T strict_mod(T lhs, T rhs) noexcept; +// Saturating shifts (clamp to max/0 on overflow) +template +constexpr T saturating_shl(T lhs, T rhs) noexcept; + +template +constexpr T saturating_shr(T lhs, T rhs) noexcept; + +// Overflowing shifts (wrap and return overflow flag) +template +constexpr std::pair overflowing_shl(T lhs, T rhs) noexcept; + +template +constexpr std::pair overflowing_shr(T lhs, T rhs) noexcept; + +// Checked shifts (return std::nullopt on overflow) +template +constexpr std::optional checked_shl(T lhs, T rhs) noexcept; + +template +constexpr std::optional checked_shr(T lhs, T rhs) noexcept; + +// Wrapping shifts (wrap without indication) +template +constexpr T wrapping_shl(T lhs, T rhs) noexcept; + +template +constexpr T wrapping_shr(T lhs, T rhs) noexcept; + +// Strict shifts (call std::exit(EXIT_FAILURE) on error) +template +constexpr T strict_shl(T lhs, T rhs) noexcept; + +template +constexpr T strict_shr(T lhs, T rhs) noexcept; + // Generic policy-parameterized arithmetic template constexpr auto add(T lhs, T rhs); @@ -241,6 +276,13 @@ constexpr auto div(T lhs, T rhs); template constexpr auto mod(T lhs, T rhs); +// Generic policy-parameterized shifts +template +constexpr auto shl(T lhs, T rhs); + +template +constexpr auto shr(T lhs, T rhs); + } // namespace boost::safe_numbers ---- @@ -395,6 +437,69 @@ The shift operators (`<<`, `>>`) perform runtime bounds checking: - `<<`: Throws `std::overflow_error` if the left shift would move bits past the type width. Specifically, this occurs when `bit_width(lhs) + rhs >= std::numeric_limits::digits`. - `>>`: Throws `std::overflow_error` if the shift amount is greater than or equal to the type width (i.e., `rhs >= std::numeric_limits::digits`). +=== Shift Overflow Policies + +In addition to the default throwing behavior, the shift operators support the same overflow policies as arithmetic operations. +The overflow condition for left shift is when `bit_width(lhs) + rhs >= digits`, and for right shift is when `rhs >= digits`. + +[source,c++] +---- +// Saturating: clamp to max (left shift) or 0 (right shift) on overflow +constexpr T saturating_shl(T lhs, T rhs) noexcept; +constexpr T saturating_shr(T lhs, T rhs) noexcept; + +// Overflowing: wrap and return an overflow flag +constexpr std::pair overflowing_shl(T lhs, T rhs) noexcept; +constexpr std::pair overflowing_shr(T lhs, T rhs) noexcept; + +// Checked: return std::nullopt on overflow +constexpr std::optional checked_shl(T lhs, T rhs) noexcept; +constexpr std::optional checked_shr(T lhs, T rhs) noexcept; + +// Wrapping: perform the shift ignoring overflow +constexpr T wrapping_shl(T lhs, T rhs) noexcept; +constexpr T wrapping_shr(T lhs, T rhs) noexcept; + +// Strict: call std::exit(EXIT_FAILURE) on overflow +constexpr T strict_shl(T lhs, T rhs) noexcept; +constexpr T strict_shr(T lhs, T rhs) noexcept; + +// Generic policy-parameterized shifts +template +constexpr auto shl(T lhs, T rhs); + +template +constexpr auto shr(T lhs, T rhs); +---- + +The behavior of each policy on shift overflow: + +|=== +| Policy | Left Shift Overflow | Right Shift Overflow + +| `saturating` +| Returns `std::numeric_limits::max()` +| Returns `0` + +| `overflowing` +| Returns the wrapped result with `true` +| Returns `0` with `true` + +| `checked` +| Returns `std::nullopt` +| Returns `std::nullopt` + +| `wrapping` +| Performs the shift (bits shifted out are lost) +| Returns `0` + +| `strict` +| Calls `std::exit(EXIT_FAILURE)` +| Calls `std::exit(EXIT_FAILURE)` +|=== + +All shift policy functions are `noexcept`. + === Compound Bitwise Assignment Operators [source,c++] @@ -441,10 +546,10 @@ Use a wrapping subtraction policy from zero or the maximum value if modular nega Operations between different width safe integer types are compile-time errors. To perform operations between different widths, explicitly convert to the same type first. -== Alternative Arithmetic Functions +== Alternative Arithmetic and Shift Functions -For cases where throwing exceptions is not desired, alternative arithmetic functions are provided with different overflow handling policies. -See xref:policies.adoc[] for full documentation of all policies, named arithmetic functions, and the generic policy-parameterized interface. +For cases where throwing exceptions is not desired, alternative arithmetic and shift functions are provided with different overflow handling policies. +See xref:policies.adoc[] for full documentation of all policies, named functions, and the generic policy-parameterized interface. == Constexpr Support diff --git a/examples/bitwise_ops.cpp b/examples/bitwise_ops.cpp index 1c37171..fe80d39 100644 --- a/examples/bitwise_ops.cpp +++ b/examples/bitwise_ops.cpp @@ -84,5 +84,81 @@ int main() std::cerr << "Right shift error: " << e.what() << '\n'; } + std::cout << '\n'; + + // ---- Policy-based shift operations ---- + + // Saturating shifts: clamp to max (left) or zero (right) on overflow + std::cout << "saturating_shl(u8(1), 4) = " + << static_cast(saturating_shl(u8{1}, u8{4})) << '\n'; + std::cout << "saturating_shl(u8(255), 1) = " + << static_cast(saturating_shl(u8{0xFF}, u8{1})) << '\n'; + std::cout << "saturating_shr(u8(128), 4) = " + << static_cast(saturating_shr(u8{0x80}, u8{4})) << '\n'; + std::cout << "saturating_shr(u8(1), 8) = " + << static_cast(saturating_shr(u8{1}, u8{8})) << '\n'; + + std::cout << '\n'; + + // Overflowing shifts: return the result and an overflow flag + { + const auto [result, flag] = overflowing_shl(u8{0xFF}, u8{1}); + std::cout << "overflowing_shl(u8(255), 1) = " + << static_cast(result) << " (overflow: " + << std::boolalpha << flag << ")\n"; + } + { + const auto [result, flag] = overflowing_shr(u8{1}, u8{8}); + std::cout << "overflowing_shr(u8(1), 8) = " + << static_cast(result) << " (overflow: " + << std::boolalpha << flag << ")\n"; + } + + std::cout << '\n'; + + // Checked shifts: return std::optional (nullopt on overflow) + { + const auto result = checked_shl(u8{1}, u8{4}); + if (result) + { + std::cout << "checked_shl(u8(1), 4) = " + << static_cast(*result) << '\n'; + } + } + { + const auto result = checked_shl(u8{0xFF}, u8{1}); + std::cout << "checked_shl(u8(255), 1) = " + << (result ? "value" : "nullopt (overflow)") << '\n'; + } + { + const auto result = checked_shr(u8{1}, u8{8}); + std::cout << "checked_shr(u8(1), 8) = " + << (result ? "value" : "nullopt (overflow)") << '\n'; + } + + std::cout << '\n'; + + // Wrapping shifts: perform the shift ignoring overflow + std::cout << "wrapping_shl(u8(255), 1) = " + << static_cast(wrapping_shl(u8{0xFF}, u8{1})) << '\n'; + std::cout << "wrapping_shr(u8(1), 8) = " + << static_cast(wrapping_shr(u8{1}, u8{8})) << '\n'; + + std::cout << '\n'; + + // Generic policy-parameterized shifts + std::cout << "shl(u32(1), 30) = " + << shl(u32{1}, u32{30}) << '\n'; + std::cout << "shl(u32(max), 1) = " + << shl(u32{0xFFFFFFFFu}, u32{1}) << '\n'; + std::cout << "shr(u32(8), 1) = "; + { + const auto result = shr(u32{8}, u32{1}); + if (result) + { + std::cout << *result << '\n'; + } + } + return 0; } diff --git a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp index 85ca565..c1ca4b3 100644 --- a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp +++ b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp @@ -1429,6 +1429,242 @@ constexpr auto unsigned_integer_basis::operator--(int) return temp; } +// ------------------------------ +// Left Shift +// ------------------------------ + +// Primary template for non-tuple policies +template +struct shl_helper +{ + [[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; + using core::bit_width; + + const auto raw_lhs {static_cast(lhs)}; + const auto raw_rhs {static_cast(rhs)}; + const auto lhs_width {static_cast(bit_width(raw_lhs))}; + const auto overflowed {lhs_width + raw_rhs >= static_cast(std::numeric_limits::digits)}; + + if (overflowed) + { + if constexpr (Policy == overflow_policy::throw_exception) + { + BOOST_THROW_EXCEPTION(std::overflow_error("Left shift past the end of the type width")); + } + else if constexpr (Policy == overflow_policy::saturate) + { + return result_type{std::numeric_limits::max()}; + } + else if constexpr (Policy == overflow_policy::strict) + { + std::exit(EXIT_FAILURE); + } + else + { + BOOST_SAFE_NUMBERS_UNREACHABLE; + } + } + + return result_type{static_cast(raw_lhs << raw_rhs)}; + } +}; + +// Partial specialization for overflow_tuple policy +template +struct shl_helper +{ + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> std::pair, bool> + { + using result_type = unsigned_integer_basis; + using core::bit_width; + + const auto raw_lhs {static_cast(lhs)}; + const auto raw_rhs {static_cast(rhs)}; + const auto lhs_width {static_cast(bit_width(raw_lhs))}; + const auto overflowed {lhs_width + raw_rhs >= static_cast(std::numeric_limits::digits)}; + + // Guard against UB: shifting by >= digits is undefined behavior + if (raw_rhs >= static_cast(std::numeric_limits::digits)) + { + return std::make_pair(result_type{0U}, true); + } + + return std::make_pair(result_type{static_cast(raw_lhs << raw_rhs)}, overflowed); + } +}; + +// Partial specialization for checked policy +template +struct shl_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; + using core::bit_width; + + const auto raw_lhs {static_cast(lhs)}; + const auto raw_rhs {static_cast(rhs)}; + const auto lhs_width {static_cast(bit_width(raw_lhs))}; + const auto overflowed {lhs_width + raw_rhs >= static_cast(std::numeric_limits::digits)}; + + return overflowed ? std::nullopt : std::make_optional(result_type{static_cast(raw_lhs << raw_rhs)}); + } +}; + +// Partial specialization for wrapping policy +template +struct shl_helper +{ + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> unsigned_integer_basis + { + using result_type = unsigned_integer_basis; + + const auto raw_lhs {static_cast(lhs)}; + const auto raw_rhs {static_cast(rhs)}; + + // Guard against UB: shifting by >= digits is undefined behavior + if (raw_rhs >= static_cast(std::numeric_limits::digits)) + { + return result_type{0U}; + } + + return result_type{static_cast(raw_lhs << raw_rhs)}; + } +}; + +template +[[nodiscard]] constexpr auto shl_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) +{ + return shl_helper::apply(lhs, rhs); +} + +// ------------------------------ +// Right Shift +// ------------------------------ + +// Primary template for non-tuple policies +template +struct shr_helper +{ + [[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 raw_lhs {static_cast(lhs)}; + const auto raw_rhs {static_cast(rhs)}; + const auto overflowed {raw_rhs >= static_cast(std::numeric_limits::digits)}; + + if (overflowed) + { + if constexpr (Policy == overflow_policy::throw_exception) + { + BOOST_THROW_EXCEPTION(std::overflow_error("Right shift past the end of the type width")); + } + else if constexpr (Policy == overflow_policy::saturate) + { + return result_type{0U}; + } + else if constexpr (Policy == overflow_policy::strict) + { + std::exit(EXIT_FAILURE); + } + else + { + BOOST_SAFE_NUMBERS_UNREACHABLE; + } + } + + return result_type{static_cast(raw_lhs >> raw_rhs)}; + } +}; + +// Partial specialization for overflow_tuple policy +template +struct shr_helper +{ + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> std::pair, bool> + { + using result_type = unsigned_integer_basis; + + const auto raw_lhs {static_cast(lhs)}; + const auto raw_rhs {static_cast(rhs)}; + const auto overflowed {raw_rhs >= static_cast(std::numeric_limits::digits)}; + + if (overflowed) + { + return std::make_pair(result_type{0U}, true); + } + + return std::make_pair(result_type{static_cast(raw_lhs >> raw_rhs)}, false); + } +}; + +// Partial specialization for checked policy +template +struct shr_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 raw_lhs {static_cast(lhs)}; + const auto raw_rhs {static_cast(rhs)}; + const auto overflowed {raw_rhs >= static_cast(std::numeric_limits::digits)}; + + return overflowed ? std::nullopt : std::make_optional(result_type{static_cast(raw_lhs >> raw_rhs)}); + } +}; + +// Partial specialization for wrapping policy +template +struct shr_helper +{ + [[nodiscard]] static constexpr auto apply(const unsigned_integer_basis lhs, + const unsigned_integer_basis rhs) noexcept + -> unsigned_integer_basis + { + using result_type = unsigned_integer_basis; + + const auto raw_lhs {static_cast(lhs)}; + const auto raw_rhs {static_cast(rhs)}; + + if (raw_rhs >= static_cast(std::numeric_limits::digits)) + { + return result_type{0U}; + } + + return result_type{static_cast(raw_lhs >> raw_rhs)}; + } +}; + +template +[[nodiscard]] constexpr auto shr_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) +{ + return shr_helper::apply(lhs, rhs); +} + } // namespace boost::safe_numbers::detail // ------------------------------ @@ -1705,6 +1941,126 @@ template BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("widening mul", widening_mul) +// ------------------------------ +// Saturating Shift +// ------------------------------ + +template +[[nodiscard]] constexpr auto saturating_shl(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> detail::unsigned_integer_basis +{ + return detail::shl_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating left shift", saturating_shl) + +template +[[nodiscard]] constexpr auto saturating_shr(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> detail::unsigned_integer_basis +{ + return detail::shr_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("saturating right shift", saturating_shr) + +// ------------------------------ +// Overflowing Shift +// ------------------------------ + +template +[[nodiscard]] constexpr auto overflowing_shl(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::pair, bool> +{ + return detail::shl_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("overflowing left shift", overflowing_shl) + +template +[[nodiscard]] constexpr auto overflowing_shr(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::pair, bool> +{ + return detail::shr_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("overflowing right shift", overflowing_shr) + +// ------------------------------ +// Checked Shift +// ------------------------------ + +template +[[nodiscard]] constexpr auto checked_shl(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::optional> +{ + return detail::shl_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("checked left shift", checked_shl) + +template +[[nodiscard]] constexpr auto checked_shr(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> std::optional> +{ + return detail::shr_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("checked right shift", checked_shr) + +// ------------------------------ +// Wrapping Shift +// ------------------------------ + +template +[[nodiscard]] constexpr auto wrapping_shl(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> detail::unsigned_integer_basis +{ + return detail::shl_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("wrapping left shift", wrapping_shl) + +template +[[nodiscard]] constexpr auto wrapping_shr(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> detail::unsigned_integer_basis +{ + return detail::shr_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("wrapping right shift", wrapping_shr) + +// ------------------------------ +// Strict Shift +// ------------------------------ + +template +[[nodiscard]] constexpr auto strict_shl(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> detail::unsigned_integer_basis +{ + return detail::shl_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("strict left shift", strict_shl) + +template +[[nodiscard]] constexpr auto strict_shr(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept + -> detail::unsigned_integer_basis +{ + return detail::shr_impl(lhs, rhs); +} + +BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP("strict right shift", strict_shr) + // ------------------------------ // Generic policy-parameterized functions // ------------------------------ @@ -1892,6 +2248,22 @@ template } } +template +[[nodiscard]] constexpr auto shl(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) + noexcept(Policy != overflow_policy::throw_exception) +{ + return detail::shl_impl(lhs, rhs); +} + +template +[[nodiscard]] constexpr auto shr(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) + noexcept(Policy != overflow_policy::throw_exception) +{ + return detail::shr_impl(lhs, rhs); +} + template constexpr auto operator~(const detail::unsigned_integer_basis lhs) noexcept { @@ -1927,36 +2299,14 @@ template constexpr auto operator<<(const detail::unsigned_integer_basis lhs, const detail::unsigned_integer_basis rhs) { - using return_type = detail::unsigned_integer_basis; - using core::bit_width; - - const auto raw_lhs {detail::raw_value(lhs)}; - const auto raw_rhs {detail::raw_value(rhs)}; - - const auto lhs_width {static_cast(bit_width(raw_lhs))}; - - if (lhs_width + raw_rhs >= std::numeric_limits::digits) - { - BOOST_THROW_EXCEPTION(std::overflow_error("Left shift past the end of the type width")); - } - - return return_type{static_cast(raw_lhs << raw_rhs)}; + return detail::shl_impl(lhs, rhs); } template constexpr auto operator>>(const detail::unsigned_integer_basis lhs, const detail::unsigned_integer_basis rhs) { - using return_type = detail::unsigned_integer_basis; - const auto raw_lhs {detail::raw_value(lhs)}; - const auto raw_rhs {detail::raw_value(rhs)}; - - if (raw_rhs >= static_cast(std::numeric_limits::digits)) - { - BOOST_THROW_EXCEPTION(std::overflow_error("Right shift past the end of the type width")); - } - - return return_type{static_cast(raw_lhs >> raw_rhs)}; + return detail::shr_impl(lhs, rhs); } // ------------------------------ diff --git a/test/test_unsigned_bitwise_ops.cpp b/test/test_unsigned_bitwise_ops.cpp index 1157c4e..eccddb3 100644 --- a/test/test_unsigned_bitwise_ops.cpp +++ b/test/test_unsigned_bitwise_ops.cpp @@ -621,6 +621,393 @@ void test_compound_right_shift_u128() BOOST_TEST_THROWS(one >>= u128{128}, std::overflow_error); } +// ============================================= +// Saturating Left Shift +// ============================================= + +template +void test_saturating_shl() +{ + using basis_type = detail::underlying_type_t; + + // Non-overflow case: should return normal result + BOOST_TEST(T{4} == saturating_shl(T{2}, T{1})); + BOOST_TEST(T{8} == saturating_shl(T{1}, T{3})); + BOOST_TEST(T{0} == saturating_shl(T{0}, T{3})); + + // Overflow case: should return max + BOOST_TEST(T{std::numeric_limits::max()} == saturating_shl(T{std::numeric_limits::max()}, T{1})); + constexpr auto digits {std::numeric_limits::digits}; + BOOST_TEST(T{std::numeric_limits::max()} == saturating_shl(T{1}, T{static_cast(digits)})); +} + +// ============================================= +// Saturating Right Shift +// ============================================= + +template +void test_saturating_shr() +{ + using basis_type = detail::underlying_type_t; + constexpr auto digits {std::numeric_limits::digits}; + + // Non-overflow case: should return normal result + BOOST_TEST(T{4} == saturating_shr(T{8}, T{1})); + BOOST_TEST(T{1} == saturating_shr(T{8}, T{3})); + BOOST_TEST(T{0} == saturating_shr(T{0}, T{3})); + + // Overflow case: shift >= digits should return 0 + BOOST_TEST(T{0} == saturating_shr(T{42}, T{static_cast(digits)})); +} + +// ============================================= +// Overflowing Left Shift +// ============================================= + +template +void test_overflowing_shl() +{ + using basis_type = detail::underlying_type_t; + constexpr auto digits {std::numeric_limits::digits}; + + // Non-overflow case + { + const auto [result, flag] {overflowing_shl(T{2}, T{1})}; + BOOST_TEST(T{4} == result); + BOOST_TEST(!flag); + } + + // Overflow case + { + const auto [result, flag] {overflowing_shl(T{std::numeric_limits::max()}, T{1})}; + BOOST_TEST(flag); + } + + // Shift by full width + { + const auto [result, flag] {overflowing_shl(T{1}, T{static_cast(digits)})}; + BOOST_TEST(flag); + } +} + +// ============================================= +// Overflowing Right Shift +// ============================================= + +template +void test_overflowing_shr() +{ + using basis_type = detail::underlying_type_t; + constexpr auto digits {std::numeric_limits::digits}; + + // Non-overflow case + { + const auto [result, flag] {overflowing_shr(T{8}, T{1})}; + BOOST_TEST(T{4} == result); + BOOST_TEST(!flag); + } + + // Overflow case: shift >= digits + { + const auto [result, flag] {overflowing_shr(T{42}, T{static_cast(digits)})}; + BOOST_TEST(T{0} == result); + BOOST_TEST(flag); + } +} + +// ============================================= +// Checked Left Shift +// ============================================= + +template +void test_checked_shl() +{ + using basis_type = detail::underlying_type_t; + constexpr auto digits {std::numeric_limits::digits}; + + // Non-overflow case: should return optional with value + { + const auto result {checked_shl(T{2}, T{1})}; + BOOST_TEST(result.has_value()); + BOOST_TEST(T{4} == result.value()); + } + + // Overflow case: should return nullopt + { + const auto result {checked_shl(T{std::numeric_limits::max()}, T{1})}; + BOOST_TEST(!result.has_value()); + } + + // Shift by full width: should return nullopt + { + const auto result {checked_shl(T{1}, T{static_cast(digits)})}; + BOOST_TEST(!result.has_value()); + } +} + +// ============================================= +// Checked Right Shift +// ============================================= + +template +void test_checked_shr() +{ + using basis_type = detail::underlying_type_t; + constexpr auto digits {std::numeric_limits::digits}; + + // Non-overflow case: should return optional with value + { + const auto result {checked_shr(T{8}, T{1})}; + BOOST_TEST(result.has_value()); + BOOST_TEST(T{4} == result.value()); + } + + // Overflow case: shift >= digits should return nullopt + { + const auto result {checked_shr(T{42}, T{static_cast(digits)})}; + BOOST_TEST(!result.has_value()); + } +} + +// ============================================= +// Wrapping Left Shift +// ============================================= + +template +void test_wrapping_shl() +{ + using basis_type = detail::underlying_type_t; + + // Non-overflow case: should return normal result + BOOST_TEST(T{4} == wrapping_shl(T{2}, T{1})); + BOOST_TEST(T{8} == wrapping_shl(T{1}, T{3})); + + // Overflow case: performs shift ignoring overflow (wraps) + const auto result {wrapping_shl(T{std::numeric_limits::max()}, T{1})}; + const auto expected {static_cast(static_cast(std::numeric_limits::max()) << 1)}; + BOOST_TEST(T{expected} == result); +} + +// ============================================= +// Wrapping Right Shift +// ============================================= + +template +void test_wrapping_shr() +{ + using basis_type = detail::underlying_type_t; + constexpr auto digits {std::numeric_limits::digits}; + + // Non-overflow case + BOOST_TEST(T{4} == wrapping_shr(T{8}, T{1})); + + // Overflow case: shift >= digits returns 0 + BOOST_TEST(T{0} == wrapping_shr(T{42}, T{static_cast(digits)})); +} + +// ============================================= +// Strict Left Shift (success cases only - failure calls exit()) +// ============================================= + +template +void test_strict_shl() +{ + // Non-overflow case + BOOST_TEST(T{4} == strict_shl(T{2}, T{1})); + BOOST_TEST(T{8} == strict_shl(T{1}, T{3})); + BOOST_TEST(T{0} == strict_shl(T{0}, T{3})); +} + +// ============================================= +// Strict Right Shift (success cases only - failure calls exit()) +// ============================================= + +template +void test_strict_shr() +{ + // Non-overflow case + BOOST_TEST(T{4} == strict_shr(T{8}, T{1})); + BOOST_TEST(T{1} == strict_shr(T{8}, T{3})); + BOOST_TEST(T{0} == strict_shr(T{0}, T{3})); +} + +// ============================================= +// Generic shl and shr +// ============================================= + +template +void test_generic_shl() +{ + using basis_type = detail::underlying_type_t; + + // throw_exception policy + BOOST_TEST(T{4} == shl(T{2}, T{1})); + + // saturate policy + BOOST_TEST(T{std::numeric_limits::max()} == shl(T{std::numeric_limits::max()}, T{1})); + + // checked policy + { + const auto result {shl(T{2}, T{1})}; + BOOST_TEST(result.has_value()); + BOOST_TEST(T{4} == result.value()); + } + + // overflow_tuple policy + { + const auto [result, flag] {shl(T{2}, T{1})}; + BOOST_TEST(T{4} == result); + BOOST_TEST(!flag); + } + + // wrapping policy + BOOST_TEST(T{4} == shl(T{2}, T{1})); + + // strict policy + BOOST_TEST(T{4} == shl(T{2}, T{1})); +} + +template +void test_generic_shr() +{ + using basis_type = detail::underlying_type_t; + constexpr auto digits {std::numeric_limits::digits}; + + // throw_exception policy + BOOST_TEST(T{4} == shr(T{8}, T{1})); + + // saturate policy + BOOST_TEST(T{0} == shr(T{42}, T{static_cast(digits)})); + + // checked policy + { + const auto result {shr(T{8}, T{1})}; + BOOST_TEST(result.has_value()); + BOOST_TEST(T{4} == result.value()); + } + + // overflow_tuple policy + { + const auto [result, flag] {shr(T{8}, T{1})}; + BOOST_TEST(T{4} == result); + BOOST_TEST(!flag); + } + + // wrapping policy + BOOST_TEST(T{4} == shr(T{8}, T{1})); + + // strict policy + BOOST_TEST(T{4} == shr(T{8}, T{1})); +} + +// ============================================= +// u128 specific shift policy tests +// ============================================= + +void test_saturating_shl_u128() +{ + using basis_type = detail::underlying_type_t; + + BOOST_TEST(u128{4} == saturating_shl(u128{2}, u128{1})); + BOOST_TEST(u128{std::numeric_limits::max()} == saturating_shl(u128{std::numeric_limits::max()}, u128{1})); + BOOST_TEST(u128{std::numeric_limits::max()} == saturating_shl(u128{1}, u128{128})); +} + +void test_saturating_shr_u128() +{ + BOOST_TEST(u128{4} == saturating_shr(u128{8}, u128{1})); + BOOST_TEST(u128{0} == saturating_shr(u128{42}, u128{128})); +} + +void test_overflowing_shl_u128() +{ + using basis_type = detail::underlying_type_t; + + { + const auto [result, flag] {overflowing_shl(u128{2}, u128{1})}; + BOOST_TEST(u128{4} == result); + BOOST_TEST(!flag); + } + { + const auto [result, flag] {overflowing_shl(u128{std::numeric_limits::max()}, u128{1})}; + BOOST_TEST(flag); + } +} + +void test_overflowing_shr_u128() +{ + { + const auto [result, flag] {overflowing_shr(u128{8}, u128{1})}; + BOOST_TEST(u128{4} == result); + BOOST_TEST(!flag); + } + { + const auto [result, flag] {overflowing_shr(u128{42}, u128{128})}; + BOOST_TEST(u128{0} == result); + BOOST_TEST(flag); + } +} + +void test_checked_shl_u128() +{ + { + const auto result {checked_shl(u128{2}, u128{1})}; + BOOST_TEST(result.has_value()); + BOOST_TEST(u128{4} == result.value()); + } + { + const auto result {checked_shl(u128{1}, u128{128})}; + BOOST_TEST(!result.has_value()); + } +} + +void test_checked_shr_u128() +{ + { + const auto result {checked_shr(u128{8}, u128{1})}; + BOOST_TEST(result.has_value()); + BOOST_TEST(u128{4} == result.value()); + } + { + const auto result {checked_shr(u128{42}, u128{128})}; + BOOST_TEST(!result.has_value()); + } +} + +void test_wrapping_shl_u128() +{ + BOOST_TEST(u128{4} == wrapping_shl(u128{2}, u128{1})); + // Wrapping: just does the shift + using basis_type = detail::underlying_type_t; + const auto result {wrapping_shl(u128{std::numeric_limits::max()}, u128{1})}; + const auto expected {static_cast(std::numeric_limits::max() << 1)}; + BOOST_TEST(u128{expected} == result); +} + +void test_wrapping_shr_u128() +{ + BOOST_TEST(u128{4} == wrapping_shr(u128{8}, u128{1})); + BOOST_TEST(u128{0} == wrapping_shr(u128{42}, u128{128})); +} + +void test_generic_shl_u128() +{ + using basis_type = detail::underlying_type_t; + + BOOST_TEST(u128{4} == shl(u128{2}, u128{1})); + BOOST_TEST(u128{std::numeric_limits::max()} == shl(u128{std::numeric_limits::max()}, u128{1})); + BOOST_TEST(u128{4} == shl(u128{2}, u128{1})); + BOOST_TEST(u128{4} == shl(u128{2}, u128{1})); +} + +void test_generic_shr_u128() +{ + BOOST_TEST(u128{4} == shr(u128{8}, u128{1})); + BOOST_TEST(u128{0} == shr(u128{42}, u128{128})); + BOOST_TEST(u128{4} == shr(u128{8}, u128{1})); + BOOST_TEST(u128{4} == shr(u128{8}, u128{1})); +} + int main() { // Bitwise NOT @@ -714,5 +1101,87 @@ int main() test_compound_right_shift(); test_compound_right_shift_u128(); + // Saturating shl + test_saturating_shl(); + test_saturating_shl(); + test_saturating_shl(); + test_saturating_shl(); + test_saturating_shl_u128(); + + // Saturating shr + test_saturating_shr(); + test_saturating_shr(); + test_saturating_shr(); + test_saturating_shr(); + test_saturating_shr_u128(); + + // Overflowing shl + test_overflowing_shl(); + test_overflowing_shl(); + test_overflowing_shl(); + test_overflowing_shl(); + test_overflowing_shl_u128(); + + // Overflowing shr + test_overflowing_shr(); + test_overflowing_shr(); + test_overflowing_shr(); + test_overflowing_shr(); + test_overflowing_shr_u128(); + + // Checked shl + test_checked_shl(); + test_checked_shl(); + test_checked_shl(); + test_checked_shl(); + test_checked_shl_u128(); + + // Checked shr + test_checked_shr(); + test_checked_shr(); + test_checked_shr(); + test_checked_shr(); + test_checked_shr_u128(); + + // Wrapping shl + test_wrapping_shl(); + test_wrapping_shl(); + test_wrapping_shl(); + test_wrapping_shl(); + test_wrapping_shl_u128(); + + // Wrapping shr + test_wrapping_shr(); + test_wrapping_shr(); + test_wrapping_shr(); + test_wrapping_shr(); + test_wrapping_shr_u128(); + + // Strict shl (success only) + test_strict_shl(); + test_strict_shl(); + test_strict_shl(); + test_strict_shl(); + + // Strict shr (success only) + test_strict_shr(); + test_strict_shr(); + test_strict_shr(); + test_strict_shr(); + + // Generic shl + test_generic_shl(); + test_generic_shl(); + test_generic_shl(); + test_generic_shl(); + test_generic_shl_u128(); + + // Generic shr + test_generic_shr(); + test_generic_shr(); + test_generic_shr(); + test_generic_shr(); + test_generic_shr_u128(); + return boost::report_errors(); }