diff --git a/doc/modules/ROOT/pages/int128_t.adoc b/doc/modules/ROOT/pages/int128_t.adoc index 2b84ba92..cb497177 100644 --- a/doc/modules/ROOT/pages/int128_t.adoc +++ b/doc/modules/ROOT/pages/int128_t.adoc @@ -377,6 +377,14 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t operator<<(const int128_t lhs, const ---- Returns the bitwise left shift of `lhs` without exception. +The shift count is the integer value of the right operand. +When the count is in the range `[0, 128)` the low bits vacated by the shift are filled with zero, bits shifted past bit 127 are discarded (the value wraps modulo 2^128), and the result is identical to the one the native 128-bit shift would produce. + +[WARNING] +==== +Shifting by a count that is negative or greater than or equal to `128` (the operand width in bits) is *undefined behavior*, exactly as for the built-in shift operators and the native `__int128` and `unsigned __int128` types (`[expr.shift]` in the C++ standard). No result is guaranteed, not even zero, and inside a constant expression the program is ill-formed. Delegating straight to the native shift this way keeps the in-range operation branchless and bit-for-bit compatible with the built-in types. +==== + When a built-in integer is the `lhs` and an `int128_t` is the shift amount, the return type matches the promotion behavior of the built-in shift: integer types of 16 bits or fewer promote to `int` or `unsigned int`, and, where the compiler provides them, the native 128-bit types return their own type. This operation is subject to mixed sign limitations discussed xref:int128_t.adoc#i128_operator_behavior[above]. @@ -404,6 +412,14 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t operator>>(const int128_t lhs, const ---- Returns the bitwise right shift of `lhs` without exception. +The shift count is the integer value of the right operand. +When the count is in the range `[0, 128)` the shift is arithmetic: the high bits vacated by the shift are filled with copies of the sign bit, so the result matches the one the native `__int128` shift would produce (dividing by a power of two and rounding toward negative infinity). + +[WARNING] +==== +Shifting by a count that is negative or greater than or equal to `128` (the operand width in bits) is *undefined behavior*, exactly as for the built-in shift operators and the native `__int128` and `unsigned __int128` types (`[expr.shift]` in the C++ standard). No result is guaranteed, not even zero or a fully sign-extended value, and inside a constant expression the program is ill-formed. Delegating straight to the native shift this way keeps the in-range operation branchless and bit-for-bit compatible with the built-in types. +==== + When a built-in integer is the `lhs` and an `int128_t` is the shift amount, the return type matches the promotion behavior of the built-in shift: integer types of 16 bits or fewer promote to `int` or `unsigned int`, and, where the compiler provides them, the native 128-bit types return their own type. This operation is subject to mixed sign limitations discussed xref:int128_t.adoc#i128_operator_behavior[above]. diff --git a/doc/modules/ROOT/pages/mixed_type_ops.adoc b/doc/modules/ROOT/pages/mixed_type_ops.adoc index 681e9dde..f822ac28 100644 --- a/doc/modules/ROOT/pages/mixed_type_ops.adoc +++ b/doc/modules/ROOT/pages/mixed_type_ops.adoc @@ -49,6 +49,7 @@ The behavior and return type of every mixed-sign overload follow the C++ usual a |=== For shift operators (`pass:[<<]`, `pass:[>>]`), the result type follows the LHS type regardless of the RHS, matching the built-in shift rules. +A shift count that is negative or greater than or equal to `128` (the width of the shifted operand) is undefined behavior, exactly as for the built-in shift operators; see the xref:int128_t.adoc#i128_bitwise_operators[shift operator] reference for the precise rules. For comparison operators (`pass:[==]`, `pass:[!=]`, `pass:[<]`, `pass:[<=]`, `pass:[>]`, `pass:[>=]`), the return type is always `bool` and the comparison is performed on the operands after they have been converted to the common type above. diff --git a/doc/modules/ROOT/pages/uint128_t.adoc b/doc/modules/ROOT/pages/uint128_t.adoc index dbc620ab..347c7d18 100644 --- a/doc/modules/ROOT/pages/uint128_t.adoc +++ b/doc/modules/ROOT/pages/uint128_t.adoc @@ -378,6 +378,14 @@ BOOST_INT128_HOST_DEVICE constexpr uint128_t operator<<(const uint128_t lhs, con ---- Returns the bitwise left shift of `lhs` without exception. +The shift count is the integer value of the right operand. +When the count is in the range `[0, 128)` the low bits vacated by the shift are filled with zero, bits shifted past bit 127 are discarded (the value wraps modulo 2^128), and the result is identical to the one the native `unsigned __int128` shift would produce. + +[WARNING] +==== +Shifting by a count that is negative or greater than or equal to `128` (the operand width in bits) is *undefined behavior*, exactly as for the built-in shift operators and the native `__int128` and `unsigned __int128` types (`[expr.shift]` in the C++ standard). No result is guaranteed, not even zero, and inside a constant expression the program is ill-formed. Delegating straight to the native shift this way keeps the in-range operation branchless and bit-for-bit compatible with the built-in types. +==== + When a built-in integer is the `lhs` and a `uint128_t` is the shift amount, the return type matches the promotion behavior of the built-in shift: integer types of 16 bits or fewer promote to `int` or `unsigned int`, and, where the compiler provides them, the native 128-bit types return their own type. This operation is subject to mixed sign limitations discussed xref:uint128_t.adoc#u128_operator_behavior[above]. @@ -405,6 +413,14 @@ BOOST_INT128_HOST_DEVICE constexpr uint128_t operator>>(const uint128_t lhs, con ---- Returns the bitwise right shift of `lhs` without exception. +The shift count is the integer value of the right operand. +When the count is in the range `[0, 128)` the shift is logical: the high bits vacated by the shift are filled with zero, so the result is identical to the one the native `unsigned __int128` shift would produce. + +[WARNING] +==== +Shifting by a count that is negative or greater than or equal to `128` (the operand width in bits) is *undefined behavior*, exactly as for the built-in shift operators and the native `__int128` and `unsigned __int128` types (`[expr.shift]` in the C++ standard). No result is guaranteed, not even zero, and inside a constant expression the program is ill-formed. Delegating straight to the native shift this way keeps the in-range operation branchless and bit-for-bit compatible with the built-in types. +==== + When a built-in integer is the `lhs` and a `uint128_t` is the shift amount, the return type matches the promotion behavior of the built-in shift: integer types of 16 bits or fewer promote to `int` or `unsigned int`, and, where the compiler provides them, the native 128-bit types return their own type. This operation is subject to mixed sign limitations discussed xref:uint128_t.adoc#u128_operator_behavior[above]. diff --git a/include/boost/int128/detail/int128_imp.hpp b/include/boost/int128/detail/int128_imp.hpp index 6c821a00..da4cfb7e 100644 --- a/include/boost/int128/detail/int128_imp.hpp +++ b/include/boost/int128/detail/int128_imp.hpp @@ -1220,21 +1220,9 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t default_ls_impl(const int128_t lhs, { static_assert(std::is_integral::value, "Only builtin types allowed"); - BOOST_INT128_IF_CONSTEXPR (std::numeric_limits::is_signed) - { - if (rhs < 0 || rhs >= 128) - { - return {0, 0}; - } - } - else - { - if (rhs >= 128) - { - return {0, 0}; - } - } - + // A shift by a negative amount or by an amount >= 128 (the operand width) is + // undefined behavior, exactly as for the built-in shift operators. In a + // constant expression the compiler diagnoses it; at runtime it is unspecified. if (rhs == 0) { return lhs; @@ -1263,21 +1251,9 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t default_ls_impl(const int128_t lhs, template BOOST_INT128_HOST_DEVICE int128_t intrinsic_ls_impl(const int128_t lhs, const Integer rhs) noexcept { - BOOST_INT128_IF_CONSTEXPR (std::numeric_limits::is_signed) - { - if (BOOST_INT128_UNLIKELY(rhs >= 128 || rhs < 0)) - { - return {0, 0}; - } - } - else - { - if (BOOST_INT128_UNLIKELY(rhs >= 128)) - { - return {0, 0}; - } - } - + // A shift by a negative amount or by an amount >= 128 (the operand width) is + // undefined behavior, exactly as for the built-in shift operators; delegate + // straight to the native type so we produce identical results. #ifdef BOOST_INT128_HAS_INT128 // Left-shifting a negative builtin_i128 is UB pre-C++20 @@ -1374,11 +1350,8 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t operator<<(const int128_t lhs, const BOOST_INT128_HOST_DEVICE constexpr int128_t operator<<(const int128_t lhs, const int128_t rhs) noexcept { - if (rhs.high != 0 || rhs.low >= 128) - { - return 0; - } - + // Out-of-range counts (negative, >= 128, or with the high word set) are + // undefined, matching the built-in operators; forward to the scalar overload. return lhs << rhs.low; } @@ -1386,25 +1359,13 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t operator<<(const int128_t lhs, const BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE BOOST_INT128_BUILTIN_CONSTEXPR detail::builtin_u128 operator<<(const detail::builtin_u128 lhs, const int128_t rhs) noexcept { - constexpr auto bit_width {sizeof(detail::builtin_u128) * 8}; - - if (rhs.high != 0 || rhs.low >= bit_width) - { - return 0; - } - + // Out-of-range counts are undefined, matching the built-in operators. return lhs << static_cast(rhs.low); } BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE BOOST_INT128_BUILTIN_CONSTEXPR detail::builtin_i128 operator<<(const detail::builtin_i128 lhs, const int128_t rhs) noexcept { - constexpr auto bit_width {sizeof(detail::builtin_i128) * 8}; - - if (rhs.high != 0 || rhs.low >= bit_width) - { - return 0; - } - + // Out-of-range counts are undefined, matching the built-in operators. return lhs << static_cast(rhs.low); } @@ -1413,26 +1374,14 @@ BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE BOOST_INT128_BUILTIN_CONSTEXPR deta BOOST_INT128_EXPORT template && (sizeof(SignedInteger) * 8 <= 16), bool> = true> BOOST_INT128_HOST_DEVICE constexpr int operator<<(const SignedInteger lhs, const int128_t rhs) noexcept { - constexpr auto bit_width {sizeof(SignedInteger) * 8}; - - if (rhs.high != 0 || rhs.low >= bit_width) - { - return 0; - } - + // Out-of-range counts are undefined, matching the built-in operators. return static_cast(lhs) << rhs.low; } BOOST_INT128_EXPORT template && (sizeof(UnsignedInteger) * 8 <= 16), bool> = true> BOOST_INT128_HOST_DEVICE constexpr unsigned operator<<(const UnsignedInteger lhs, const int128_t rhs) noexcept { - constexpr auto bit_width {sizeof(UnsignedInteger) * 8}; - - if (rhs.high != 0 || rhs.low >= bit_width) - { - return 0; - } - + // Out-of-range counts are undefined, matching the built-in operators. return static_cast(lhs) << rhs.low; } @@ -1478,21 +1427,9 @@ namespace detail { template BOOST_INT128_HOST_DEVICE constexpr int128_t default_rs_impl(const int128_t lhs, const Integer rhs) noexcept { - BOOST_INT128_IF_CONSTEXPR (std::numeric_limits::is_signed) - { - if (rhs >= 128 || rhs < 0) - { - return lhs.high < 0 ? int128_t{-1, UINT64_MAX} : int128_t{0, 0}; - } - } - else - { - if (rhs >= 128) - { - return lhs.high < 0 ? int128_t{-1, UINT64_MAX} : int128_t{0, 0}; - } - } - + // A shift by a negative amount or by an amount >= 128 (the operand width) is + // undefined behavior, exactly as for the built-in shift operators. In a + // constant expression the compiler diagnoses it; at runtime it is unspecified. if (rhs == 0) { return lhs; @@ -1517,21 +1454,9 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t default_rs_impl(const int128_t lhs, template BOOST_INT128_HOST_DEVICE int128_t intrinsic_rs_impl(const int128_t lhs, const Integer rhs) noexcept { - BOOST_INT128_IF_CONSTEXPR (std::numeric_limits::is_signed) - { - if (rhs >= 128 || rhs < 0) - { - return lhs.high < 0 ? int128_t{-1, UINT64_MAX} : int128_t{0, 0}; - } - } - else - { - if (rhs >= 128) - { - return lhs.high < 0 ? int128_t{-1, UINT64_MAX} : int128_t{0, 0}; - } - } - + // A shift by a negative amount or by an amount >= 128 (the operand width) is + // undefined behavior, exactly as for the built-in shift operators; delegate + // straight to the native type so we produce identical results. #ifdef BOOST_INT128_HAS_INT128 # if defined(__aarch64__) @@ -1624,11 +1549,8 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t operator>>(const int128_t lhs, const BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int128_t operator>>(const int128_t lhs, const int128_t rhs) noexcept { - if (rhs.high != 0 || rhs.low >= 128) - { - return 0; - } - + // Out-of-range counts (negative, >= 128, or with the high word set) are + // undefined, matching the built-in operators; forward to the scalar overload. return lhs >> rhs.low; } @@ -1636,25 +1558,13 @@ BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int128_t operator>>(const BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE BOOST_INT128_BUILTIN_CONSTEXPR detail::builtin_u128 operator>>(const detail::builtin_u128 lhs, const int128_t rhs) noexcept { - constexpr auto bit_width {sizeof(detail::builtin_u128) * 8}; - - if (rhs.high != 0 || rhs.low >= bit_width) - { - return 0; - } - + // Out-of-range counts are undefined, matching the built-in operators. return lhs >> static_cast(rhs.low); } BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE BOOST_INT128_BUILTIN_CONSTEXPR detail::builtin_i128 operator>>(const detail::builtin_i128 lhs, const int128_t rhs) noexcept { - constexpr auto bit_width {sizeof(detail::builtin_i128) * 8}; - - if (rhs.high != 0 || rhs.low >= bit_width) - { - return 0; - } - + // Out-of-range counts are undefined, matching the built-in operators. return lhs >> static_cast(rhs.low); } @@ -1663,26 +1573,14 @@ BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE BOOST_INT128_BUILTIN_CONSTEXPR deta BOOST_INT128_EXPORT template && (sizeof(SignedInteger) * 8 <= 16), bool> = true> BOOST_INT128_HOST_DEVICE constexpr int operator>>(const SignedInteger lhs, const int128_t rhs) noexcept { - constexpr auto bit_width {sizeof(SignedInteger) * 8}; - - if (rhs.high != 0 || rhs.low >= bit_width) - { - return 0; - } - + // Out-of-range counts are undefined, matching the built-in operators. return static_cast(lhs) >> rhs.low; } BOOST_INT128_EXPORT template && (sizeof(UnsignedInteger) * 8 <= 16), bool> = true> BOOST_INT128_HOST_DEVICE constexpr unsigned operator>>(const UnsignedInteger lhs, const int128_t rhs) noexcept { - constexpr auto bit_width {sizeof(UnsignedInteger) * 8}; - - if (rhs.high != 0 || rhs.low >= bit_width) - { - return 0; - } - + // Out-of-range counts are undefined, matching the built-in operators. return static_cast(lhs) >> rhs.low; } diff --git a/include/boost/int128/detail/uint128_imp.hpp b/include/boost/int128/detail/uint128_imp.hpp index 2af78cdc..fc1e9c0d 100644 --- a/include/boost/int128/detail/uint128_imp.hpp +++ b/include/boost/int128/detail/uint128_imp.hpp @@ -1417,21 +1417,9 @@ BOOST_INT128_HOST_DEVICE constexpr uint128_t default_ls_impl(const uint128_t lhs { static_assert(std::is_integral::value, "Needs to be a builtin type"); - BOOST_INT128_IF_CONSTEXPR (std::numeric_limits::is_signed) - { - if (rhs < 0 || rhs >= 128) - { - return {0, 0}; - } - } - else - { - if (rhs >= 128) - { - return {0, 0}; - } - } - + // A shift by a negative amount or by an amount >= 128 (the operand width) is + // undefined behavior, exactly as for the built-in shift operators. In a + // constant expression the compiler diagnoses it; at runtime it is unspecified. if (rhs == 0) { return lhs; @@ -1456,26 +1444,9 @@ BOOST_INT128_HOST_DEVICE constexpr uint128_t default_ls_impl(const uint128_t lhs template BOOST_INT128_HOST_DEVICE uint128_t intrinsic_ls_impl(const uint128_t lhs, const T rhs) noexcept { - BOOST_INT128_IF_CONSTEXPR (std::numeric_limits::is_signed) - { - if (BOOST_INT128_UNLIKELY(rhs >= 128 || rhs < 0)) - { - return {0, 0}; - } - } - else - { - if (BOOST_INT128_UNLIKELY(rhs >= 128)) - { - return {0, 0}; - } - } - - if (BOOST_INT128_UNLIKELY(rhs == 0)) - { - return lhs; - } - + // A shift by a negative amount or by an amount >= 128 (the operand width) is + // undefined behavior, exactly as for the built-in shift operators; delegate + // straight to the native type so we produce identical results. #ifdef BOOST_INT128_HAS_INT128 # if defined(__aarch64__) @@ -1505,6 +1476,11 @@ BOOST_INT128_HOST_DEVICE uint128_t intrinsic_ls_impl(const uint128_t lhs, const #else + if (rhs == 0) + { + return lhs; + } + if (rhs == 64) { return {lhs.low, 0}; @@ -1550,11 +1526,8 @@ BOOST_INT128_HOST_DEVICE constexpr uint128_t operator<<(const uint128_t lhs, con BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr uint128_t operator<<(const uint128_t lhs, const uint128_t rhs) noexcept { - if (rhs.high > UINT64_C(0) || rhs.low >= UINT64_C(128)) - { - return uint128_t{0}; - } - + // Out-of-range counts (>= 128 or with the high word set) are undefined, + // matching the built-in operators; forward the count to the scalar overload. return lhs << rhs.low; } @@ -1562,24 +1535,13 @@ BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr uint128_t operator<<(cons BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE BOOST_INT128_BUILTIN_CONSTEXPR detail::builtin_u128 operator<<(const detail::builtin_u128 lhs, const uint128_t rhs) noexcept { - constexpr auto bit_width {sizeof(detail::builtin_u128 ) * 8}; - - if (rhs.high > UINT64_C(0) || rhs.low >= bit_width) - { - return 0; - } + // Out-of-range counts are undefined, matching the built-in operators. return lhs << static_cast(rhs.low); } BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE BOOST_INT128_BUILTIN_CONSTEXPR detail::builtin_i128 operator<<(const detail::builtin_i128 lhs, const uint128_t rhs) noexcept { - constexpr auto bit_width {sizeof(detail::builtin_u128) * 8}; - - if (rhs.high > UINT64_C(0) || rhs.low >= bit_width) - { - return 0; - } - + // Out-of-range counts are undefined, matching the built-in operators. return lhs << static_cast(rhs.low); } @@ -1588,26 +1550,14 @@ BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE BOOST_INT128_BUILTIN_CONSTEXPR deta BOOST_INT128_EXPORT template && (sizeof(SignedInteger) * 8 <= 16), bool> = true> BOOST_INT128_HOST_DEVICE constexpr int operator<<(const SignedInteger lhs, const uint128_t rhs) noexcept { - constexpr auto bit_width {sizeof(SignedInteger) * 8}; - - if (rhs.high > UINT64_C(0) || rhs.low >= bit_width) - { - return 0; - } - + // Out-of-range counts are undefined, matching the built-in operators. return static_cast(lhs) << rhs.low; } BOOST_INT128_EXPORT template && (sizeof(UnsignedInteger) * 8 <= 16), bool> = true> BOOST_INT128_HOST_DEVICE constexpr unsigned int operator<<(const UnsignedInteger lhs, const uint128_t rhs) noexcept { - constexpr auto bit_width {sizeof(UnsignedInteger) * 8}; - - if (rhs.high > UINT64_C(0) || rhs.low >= bit_width) - { - return 0; - } - + // Out-of-range counts are undefined, matching the built-in operators. return static_cast(lhs) << rhs.low; } @@ -1644,21 +1594,9 @@ namespace detail { template BOOST_INT128_HOST_DEVICE constexpr uint128_t default_rs_impl(const uint128_t lhs, const Integer rhs) noexcept { - BOOST_INT128_IF_CONSTEXPR (std::numeric_limits::is_signed) - { - if (rhs < 0 || rhs >= 128) - { - return {0, 0}; - } - } - else - { - if (rhs >= 128) - { - return {0, 0}; - } - } - + // A shift by a negative amount or by an amount >= 128 (the operand width) is + // undefined behavior, exactly as for the built-in shift operators. In a + // constant expression the compiler diagnoses it; at runtime it is unspecified. if (rhs == 0) { return lhs; @@ -1683,26 +1621,9 @@ BOOST_INT128_HOST_DEVICE constexpr uint128_t default_rs_impl(const uint128_t lhs template BOOST_INT128_HOST_DEVICE uint128_t intrinsic_rs_impl(const uint128_t lhs, const Integer rhs) noexcept { - BOOST_INT128_IF_CONSTEXPR (std::numeric_limits::is_signed) - { - if (BOOST_INT128_UNLIKELY(rhs >= 128 || rhs < 0)) - { - return {0, 0}; - } - } - else - { - if (BOOST_INT128_UNLIKELY(rhs >= 128)) - { - return {0, 0}; - } - } - - if (BOOST_INT128_UNLIKELY(rhs == 0)) - { - return lhs; - } - + // A shift by a negative amount or by an amount >= 128 (the operand width) is + // undefined behavior, exactly as for the built-in shift operators; delegate + // straight to the native type so we produce identical results. #ifdef BOOST_INT128_HAS_INT128 # ifdef __aarch64__ @@ -1730,6 +1651,11 @@ BOOST_INT128_HOST_DEVICE uint128_t intrinsic_rs_impl(const uint128_t lhs, const #else + if (rhs == 0) + { + return lhs; + } + if (rhs == 64) { return {0, lhs.high}; @@ -1772,11 +1698,8 @@ BOOST_INT128_HOST_DEVICE constexpr uint128_t operator>>(const uint128_t lhs, con BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr uint128_t operator>>(const uint128_t lhs, const uint128_t rhs) noexcept { - if (rhs.high > UINT64_C(0) || rhs.low >= UINT64_C(128)) - { - return uint128_t{0}; - } - + // Out-of-range counts (>= 128 or with the high word set) are undefined, + // matching the built-in operators; forward the count to the scalar overload. return lhs >> rhs.low; } @@ -1784,25 +1707,13 @@ BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr uint128_t operator>>(cons BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE BOOST_INT128_BUILTIN_CONSTEXPR detail::builtin_u128 operator>>(const detail::builtin_u128 lhs, const uint128_t rhs) noexcept { - constexpr auto bit_width = sizeof(detail::builtin_u128) * 8; - - if (rhs.high > UINT64_C(0) || rhs.low >= bit_width) - { - return 0; - } - + // Out-of-range counts are undefined, matching the built-in operators. return lhs >> static_cast(rhs.low); } BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE BOOST_INT128_BUILTIN_CONSTEXPR detail::builtin_i128 operator>>(const detail::builtin_i128 lhs, const uint128_t rhs) noexcept { - constexpr auto bit_width = sizeof(detail::builtin_i128) * 8; - - if (rhs.high > UINT64_C(0) || rhs.low >= bit_width) - { - return 0; - } - + // Out-of-range counts are undefined, matching the built-in operators. return lhs >> static_cast(rhs.low); } @@ -1811,26 +1722,14 @@ BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE BOOST_INT128_BUILTIN_CONSTEXPR deta BOOST_INT128_EXPORT template && (sizeof(SignedInteger) * 8 <= 16), bool> = true> BOOST_INT128_HOST_DEVICE constexpr int operator>>(const SignedInteger lhs, const uint128_t rhs) noexcept { - constexpr auto bit_width = sizeof(SignedInteger) * 8; - - if (rhs.high > UINT64_C(0) || rhs.low >= bit_width) - { - return 0; - } - + // Out-of-range counts are undefined, matching the built-in operators. return static_cast(lhs) >> rhs.low; } BOOST_INT128_EXPORT template && (sizeof(UnsignedInteger) * 8 <= 16), bool> = true> BOOST_INT128_HOST_DEVICE constexpr unsigned operator>>(UnsignedInteger lhs, const uint128_t rhs) noexcept { - constexpr auto bit_width = sizeof(UnsignedInteger) * 8; - - if (rhs.high > UINT64_C(0) || rhs.low >= bit_width) - { - return 0; - } - + // Out-of-range counts are undefined, matching the built-in operators. return static_cast(lhs) >> rhs.low; } diff --git a/test/test_i128.cpp b/test/test_i128.cpp index 608fac83..ad053d68 100644 --- a/test/test_i128.cpp +++ b/test/test_i128.cpp @@ -673,13 +673,17 @@ void test_operator_left_shift() BOOST_TEST(consteval_result == shifted_emulated); } - // Edge cases + // Edge cases. A shift by a negative amount or by an amount >= 128 is + // undefined behavior, exactly as for the built-in shift operators (see the + // documentation), so only the well-defined in-range counts are exercised. + // The constant-evaluated (default_ls_impl) path must agree with the runtime + // path for every in-range count. const boost::int128::int128_t val {UINT64_MAX}; - BOOST_TEST((val << 130) == 0); - BOOST_TEST((val << -5) == 0); - - BOOST_TEST(boost::int128::detail::default_ls_impl(val, 130) == 0); - BOOST_TEST(boost::int128::detail::default_ls_impl(val, -5) == 0); + BOOST_TEST((val << 0) == val); + for (unsigned s {}; s < 128U; ++s) + { + BOOST_TEST(boost::int128::detail::default_ls_impl(val, s) == (val << s)); + } } template @@ -726,13 +730,19 @@ void test_operator_right_shift() BOOST_TEST(consteval_result == shifted_emulated); } - // Edge cases + // Edge cases. A shift by a negative amount or by an amount >= 128 is + // undefined behavior, exactly as for the built-in shift operators (see the + // documentation), so only the well-defined in-range counts are exercised. + // Both a positive and a negative value are checked so the arithmetic + // (sign-extending) right shift is covered for every in-range count. const boost::int128::int128_t val {UINT64_MAX}; - BOOST_TEST((val >> 130) == 0); - BOOST_TEST((val >> -5) == 0); - - BOOST_TEST(boost::int128::detail::default_rs_impl(val, 130) == 0); - BOOST_TEST(boost::int128::detail::default_rs_impl(val, -5) == 0); + const boost::int128::int128_t neg {-(boost::int128::int128_t{1} << 96)}; + BOOST_TEST((val >> 0) == val); + for (unsigned s {}; s < 128U; ++s) + { + BOOST_TEST(boost::int128::detail::default_rs_impl(val, s) == (val >> s)); + BOOST_TEST(boost::int128::detail::default_rs_impl(neg, s) == (neg >> s)); + } } void test_increment_operator() diff --git a/test/test_i128_no_sign_conv.cpp b/test/test_i128_no_sign_conv.cpp index ba2a289e..299156f2 100644 --- a/test/test_i128_no_sign_conv.cpp +++ b/test/test_i128_no_sign_conv.cpp @@ -592,11 +592,6 @@ void test_operator_left_shift() BOOST_TEST(int_shift_emulated == int_shift_builtin); // LCOV_EXCL_LINE } - - // Edge cases - const boost::int128::int128_t val {UINT64_MAX}; - BOOST_TEST((val << 130) == 0); // LCOV_EXCL_LINE - BOOST_TEST((val << -5) == 0); // LCOV_EXCL_LINE } template @@ -638,11 +633,6 @@ void test_operator_right_shift() BOOST_TEST(int_shift_emulated == int_shift_builtin); // LCOV_EXCL_LINE } - - // Edge cases - const boost::int128::int128_t val {UINT64_MAX}; - BOOST_TEST((val >> 130) == 0); // LCOV_EXCL_LINE - BOOST_TEST((val >> -5) == 0); // LCOV_EXCL_LINE } void test_increment_operator() diff --git a/test/test_u128.cpp b/test/test_u128.cpp index 6d219560..305cdc4f 100644 --- a/test/test_u128.cpp +++ b/test/test_u128.cpp @@ -825,27 +825,22 @@ void test_operator_left_shift() BOOST_TEST(shifted_emulated == shift_value_consteval); } - // Edge cases + // Edge cases. A shift by a negative amount or by an amount >= 128 is + // undefined behavior, exactly as for the built-in shift operators (see the + // documentation), so only the well-defined in-range counts are exercised. + // The scalar, uint128_t-count, and consteval paths must all agree. const boost::int128::uint128_t val {UINT64_MAX}; - BOOST_TEST((val << 130) == 0); - auto res {val << static_cast(128)}; - BOOST_TEST(res == static_cast(0U)); - BOOST_TEST((val << -5) == 0); - res = (val << static_cast(0)); - BOOST_TEST(res == val); - - BOOST_TEST(boost::int128::detail::default_ls_impl(val, 130) == 0); - BOOST_TEST(boost::int128::detail::default_ls_impl(val, -5) == 0); - BOOST_TEST(boost::int128::detail::default_ls_impl(val, 0) == val); - - auto builtin_value {static_cast(dist(rng))}; - boost::int128::uint128_t small_shift {1u}; - boost::int128::uint128_t big_shift {180u}; - boost::int128::uint128_t biggest_shift {1u, 180u}; + BOOST_TEST((val << 0) == val); + BOOST_TEST((val << static_cast(0)) == val); + for (unsigned s {}; s < 128U; ++s) + { + BOOST_TEST(boost::int128::detail::default_ls_impl(val, s) == (val << s)); + BOOST_TEST((val << static_cast(s)) == (val << s)); + } + const auto builtin_value {static_cast(dist(rng))}; + const boost::int128::uint128_t small_shift {1u}; BOOST_TEST((builtin_value << small_shift) == (builtin_value << 1u)); - BOOST_TEST((builtin_value << big_shift) == 0u); - BOOST_TEST((builtin_value << biggest_shift) == 0u); } template @@ -893,27 +888,22 @@ void test_operator_right_shift() BOOST_TEST(shifted_emulated == shift_value_consteval); } - // Edge cases + // Edge cases. A shift by a negative amount or by an amount >= 128 is + // undefined behavior, exactly as for the built-in shift operators (see the + // documentation), so only the well-defined in-range counts are exercised. + // The scalar, uint128_t-count, and consteval paths must all agree. const boost::int128::uint128_t val {UINT64_MAX}; - BOOST_TEST((val >> 130) == 0); - BOOST_TEST((val >> -5) == 0); - auto res {val >> static_cast(128)}; - BOOST_TEST(res == static_cast(0U)); - res = (val >> static_cast(0)); - BOOST_TEST(res == val); - - BOOST_TEST(boost::int128::detail::default_rs_impl(val, 130) == 0); - BOOST_TEST(boost::int128::detail::default_rs_impl(val, -5) == 0); - BOOST_TEST(boost::int128::detail::default_rs_impl(val, 0) == val); - - auto builtin_value {static_cast(dist(rng))}; - boost::int128::uint128_t small_shift {1u}; - boost::int128::uint128_t big_shift {180u}; - boost::int128::uint128_t biggest_shift {1u, 180u}; + BOOST_TEST((val >> 0) == val); + BOOST_TEST((val >> static_cast(0)) == val); + for (unsigned s {}; s < 128U; ++s) + { + BOOST_TEST(boost::int128::detail::default_rs_impl(val, s) == (val >> s)); + BOOST_TEST((val >> static_cast(s)) == (val >> s)); + } + const auto builtin_value {static_cast(dist(rng))}; + const boost::int128::uint128_t small_shift {1u}; BOOST_TEST((builtin_value >> small_shift) == (builtin_value >> 1u)); - BOOST_TEST((builtin_value >> big_shift) == 0u); - BOOST_TEST((builtin_value >> biggest_shift) == 0u); } void test_increment_operator() diff --git a/test/test_u128_no_sign_conv.cpp b/test/test_u128_no_sign_conv.cpp index 729161ac..7b1c4490 100644 --- a/test/test_u128_no_sign_conv.cpp +++ b/test/test_u128_no_sign_conv.cpp @@ -643,11 +643,6 @@ void test_operator_left_shift() BOOST_TEST(int_shift_emulated == int_shift_builtin); // LCOV_EXCL_LINE } - - // Edge cases - const boost::int128::uint128_t val {UINT64_MAX}; - BOOST_TEST((val << 130) == 0U); // LCOV_EXCL_LINE - BOOST_TEST((val << -5) == 0U); // LCOV_EXCL_LINE } template @@ -689,11 +684,6 @@ void test_operator_right_shift() BOOST_TEST(int_shift_emulated == int_shift_builtin); // LCOV_EXCL_LINE } - - // Edge cases - const boost::int128::uint128_t val {UINT64_MAX}; - BOOST_TEST((val << 130) == 0U); // LCOV_EXCL_LINE - BOOST_TEST((val << -5) == 0U); // LCOV_EXCL_LINE } void test_increment_operator()