Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions doc/modules/ROOT/pages/int128_t.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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].

Expand Down Expand Up @@ -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].

Expand Down
1 change: 1 addition & 0 deletions doc/modules/ROOT/pages/mixed_type_ops.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
16 changes: 16 additions & 0 deletions doc/modules/ROOT/pages/uint128_t.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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].

Expand Down Expand Up @@ -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].

Expand Down
150 changes: 24 additions & 126 deletions include/boost/int128/detail/int128_imp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1220,21 +1220,9 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t default_ls_impl(const int128_t lhs,
{
static_assert(std::is_integral<Integer>::value, "Only builtin types allowed");

BOOST_INT128_IF_CONSTEXPR (std::numeric_limits<Integer>::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;
Expand Down Expand Up @@ -1263,21 +1251,9 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t default_ls_impl(const int128_t lhs,
template <typename Integer>
BOOST_INT128_HOST_DEVICE int128_t intrinsic_ls_impl(const int128_t lhs, const Integer rhs) noexcept
{
BOOST_INT128_IF_CONSTEXPR (std::numeric_limits<Integer>::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
Expand Down Expand Up @@ -1374,37 +1350,22 @@ 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;
}

#if defined(BOOST_INT128_HAS_INT128) || defined(BOOST_INT128_HAS_MSVC_INT128)

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<detail::builtin_u128>(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<detail::builtin_u128>(rhs.low);
}

Expand All @@ -1413,26 +1374,14 @@ BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE BOOST_INT128_BUILTIN_CONSTEXPR deta
BOOST_INT128_EXPORT template <typename SignedInteger, std::enable_if_t<detail::is_signed_integer_v<SignedInteger> && (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<int>(lhs) << rhs.low;
}

BOOST_INT128_EXPORT template <typename UnsignedInteger, std::enable_if_t<detail::is_unsigned_integer_v<UnsignedInteger> && (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<unsigned>(lhs) << rhs.low;
}

Expand Down Expand Up @@ -1478,21 +1427,9 @@ namespace detail {
template <typename Integer>
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<Integer>::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;
Expand All @@ -1517,21 +1454,9 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t default_rs_impl(const int128_t lhs,
template <typename Integer>
BOOST_INT128_HOST_DEVICE int128_t intrinsic_rs_impl(const int128_t lhs, const Integer rhs) noexcept
{
BOOST_INT128_IF_CONSTEXPR (std::numeric_limits<Integer>::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__)
Expand Down Expand Up @@ -1624,37 +1549,22 @@ 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;
}

#if defined(BOOST_INT128_HAS_INT128) || defined(BOOST_INT128_HAS_MSVC_INT128)

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<detail::builtin_u128>(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<detail::builtin_u128>(rhs.low);
}

Expand All @@ -1663,26 +1573,14 @@ BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE BOOST_INT128_BUILTIN_CONSTEXPR deta
BOOST_INT128_EXPORT template <typename SignedInteger, std::enable_if_t<detail::is_signed_integer_v<SignedInteger> && (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<int>(lhs) >> rhs.low;
}

BOOST_INT128_EXPORT template <typename UnsignedInteger, std::enable_if_t<detail::is_unsigned_integer_v<UnsignedInteger> && (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<unsigned>(lhs) >> rhs.low;
}

Expand Down
Loading
Loading