From 21ecf03b4f7170a9a30805054588c6ad8683edbb Mon Sep 17 00:00:00 2001
From: David Li
Date: Mon, 14 Jun 2021 11:40:57 -0400
Subject: [PATCH 1/6] ARROW-13072: [C++] Add bit-wise arithmetic kernels
---
cpp/src/arrow/compute/api_scalar.cc | 2 +
cpp/src/arrow/compute/api_scalar.h | 27 ++
.../compute/kernels/scalar_arithmetic.cc | 268 ++++++++++++++++++
.../compute/kernels/scalar_arithmetic_test.cc | 201 +++++++++++++
docs/source/cpp/compute.rst | 23 ++
docs/source/python/api/compute.rst | 14 +
6 files changed, 535 insertions(+)
diff --git a/cpp/src/arrow/compute/api_scalar.cc b/cpp/src/arrow/compute/api_scalar.cc
index db1cac290cf..f005e70e348 100644
--- a/cpp/src/arrow/compute/api_scalar.cc
+++ b/cpp/src/arrow/compute/api_scalar.cc
@@ -62,6 +62,8 @@ SCALAR_ARITHMETIC_BINARY(Subtract, "subtract", "subtract_checked")
SCALAR_ARITHMETIC_BINARY(Multiply, "multiply", "multiply_checked")
SCALAR_ARITHMETIC_BINARY(Divide, "divide", "divide_checked")
SCALAR_ARITHMETIC_BINARY(Power, "power", "power_checked")
+SCALAR_ARITHMETIC_BINARY(ShiftLeft, "shift_left", "shift_left_checked")
+SCALAR_ARITHMETIC_BINARY(ShiftRight, "shift_right", "shift_right_checked")
Result MaxElementWise(const std::vector& args,
ElementWiseAggregateOptions options, ExecContext* ctx) {
diff --git a/cpp/src/arrow/compute/api_scalar.h b/cpp/src/arrow/compute/api_scalar.h
index 082876b356b..146aec3b92e 100644
--- a/cpp/src/arrow/compute/api_scalar.h
+++ b/cpp/src/arrow/compute/api_scalar.h
@@ -298,6 +298,33 @@ Result Power(const Datum& left, const Datum& right,
ArithmeticOptions options = ArithmeticOptions(),
ExecContext* ctx = NULLPTR);
+/// \brief Left shift the left array by the right array. Array values must be the
+/// same length. If either operand is null, the result will be null.
+///
+/// \param[in] left the value to shift
+/// \param[in] right the value to shift by
+/// \param[in] options arithmetic options (enable/disable overflow checking), optional
+/// \param[in] ctx the function execution context, optional
+/// \return the elementwise left value shifted left by the right value
+ARROW_EXPORT
+Result ShiftLeft(const Datum& left, const Datum& right,
+ ArithmeticOptions options = ArithmeticOptions(),
+ ExecContext* ctx = NULLPTR);
+
+/// \brief Right shift the left array by the right array. Array values must be the
+/// same length. If either operand is null, the result will be null. Performs a
+/// logical shift for unsigned values, and an arithmetic shift for signed values.
+///
+/// \param[in] left the value to shift
+/// \param[in] right the value to shift by
+/// \param[in] options arithmetic options (enable/disable overflow checking), optional
+/// \param[in] ctx the function execution context, optional
+/// \return the elementwise left value shifted right by the right value
+ARROW_EXPORT
+Result ShiftRight(const Datum& left, const Datum& right,
+ ArithmeticOptions options = ArithmeticOptions(),
+ ExecContext* ctx = NULLPTR);
+
/// \brief Find the element-wise maximum of any number of arrays or scalars.
/// Array values must be the same length.
///
diff --git a/cpp/src/arrow/compute/kernels/scalar_arithmetic.cc b/cpp/src/arrow/compute/kernels/scalar_arithmetic.cc
index f51484e53ff..8a1e5639a7a 100644
--- a/cpp/src/arrow/compute/kernels/scalar_arithmetic.cc
+++ b/cpp/src/arrow/compute/kernels/scalar_arithmetic.cc
@@ -454,6 +454,114 @@ struct PowerChecked {
}
};
+// Bitwise operations
+
+struct BitWiseNot {
+ template
+ static T Call(KernelContext*, Arg arg, Status*) {
+ return ~arg;
+ }
+};
+
+struct BitWiseAnd {
+ template
+ static T Call(KernelContext*, Arg0 lhs, Arg1 rhs, Status*) {
+ return lhs & rhs;
+ }
+};
+
+struct BitWiseOr {
+ template
+ static T Call(KernelContext*, Arg0 lhs, Arg1 rhs, Status*) {
+ return lhs | rhs;
+ }
+};
+
+struct BitWiseXor {
+ template
+ static T Call(KernelContext*, Arg0 lhs, Arg1 rhs, Status*) {
+ return lhs ^ rhs;
+ }
+};
+
+struct ShiftLeft {
+ template
+ static T Call(KernelContext*, Arg0 lhs, Arg1 rhs, Status*) {
+ using Unsigned = typename std::make_unsigned::type;
+ static_assert(std::is_same::value, "");
+ if (ARROW_PREDICT_FALSE(rhs < 0 || rhs >= std::numeric_limits::digits)) {
+ return lhs;
+ }
+ return static_cast(static_cast(lhs) << static_cast(rhs));
+ }
+};
+
+// See SEI CERT C Coding Standard rule INT34-C
+struct ShiftLeftChecked {
+ template
+ static enable_if_unsigned_integer Call(KernelContext*, Arg0 lhs, Arg1 rhs,
+ Status* st) {
+ static_assert(std::is_same::value, "");
+ if (ARROW_PREDICT_FALSE(rhs < 0)) {
+ *st = Status::Invalid("shift must be non-negative");
+ return lhs;
+ }
+ if (ARROW_PREDICT_FALSE(rhs >= std::numeric_limits::digits)) {
+ *st = Status::Invalid("overflow");
+ return lhs;
+ }
+ return lhs << rhs;
+ }
+
+ template
+ static enable_if_signed_integer Call(KernelContext*, Arg0 lhs, Arg1 rhs,
+ Status* st) {
+ using Unsigned = typename std::make_unsigned::type;
+ static_assert(std::is_same::value, "");
+ if (ARROW_PREDICT_FALSE(rhs < 0)) {
+ *st = Status::Invalid("shift must be non-negative");
+ return lhs;
+ }
+ if (ARROW_PREDICT_FALSE(rhs >= std::numeric_limits::digits)) {
+ *st = Status::Invalid("overflow");
+ return lhs;
+ }
+ // In C/C++ left shift of a negative number is undefined (C++11 standard 5.8.2)
+ // Mimic Java/etc. and treat left shift as based on two's complement representation
+ // Assumes two's complement machine
+ return static_cast(static_cast(lhs) << static_cast(rhs));
+ }
+};
+
+struct ShiftRight {
+ template
+ static T Call(KernelContext*, Arg0 lhs, Arg1 rhs, Status*) {
+ static_assert(std::is_same::value, "");
+ // Logical right shift when Arg0 is unsigned
+ // Arithmetic otherwise (this is implementation-defined but GCC and MSVC document this
+ // as arithmetic right shift)
+ // https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation
+ // https://docs.microsoft.com/en-us/cpp/cpp/left-shift-and-right-shift-operators-input-and-output?view=msvc-160
+ // Clang doesn't document their behavior.
+ if (ARROW_PREDICT_FALSE(rhs < 0 || rhs >= std::numeric_limits::digits)) {
+ return lhs;
+ }
+ return lhs >> rhs;
+ }
+};
+
+struct ShiftRightChecked {
+ template
+ static T Call(KernelContext*, Arg0 lhs, Arg1 rhs, Status* st) {
+ static_assert(std::is_same::value, "");
+ if (ARROW_PREDICT_FALSE(rhs < 0 || rhs >= std::numeric_limits::digits)) {
+ *st = Status::Invalid("rhs must be >= 0 and less than precision of type");
+ return lhs;
+ }
+ return lhs >> rhs;
+ }
+};
+
// Generate a kernel given an arithmetic functor
template class KernelGenerator, typename Op>
ArrayKernelExec ArithmeticExecFromOp(detail::GetTypeId get_id) {
@@ -485,6 +593,54 @@ ArrayKernelExec ArithmeticExecFromOp(detail::GetTypeId get_id) {
}
}
+// Generate a kernel given a bitwise arithmetic functor. Assumes the
+// functor treats all integer types of equal width identically
+template class KernelGenerator, typename Op>
+ArrayKernelExec TypeAgnosticBitWiseExecFromOp(detail::GetTypeId get_id) {
+ switch (get_id.id) {
+ case Type::INT8:
+ case Type::UINT8:
+ return KernelGenerator::Exec;
+ case Type::INT16:
+ case Type::UINT16:
+ return KernelGenerator::Exec;
+ case Type::INT32:
+ case Type::UINT32:
+ return KernelGenerator::Exec;
+ case Type::INT64:
+ case Type::UINT64:
+ return KernelGenerator::Exec;
+ default:
+ DCHECK(false);
+ return ExecFail;
+ }
+}
+
+template class KernelGenerator, typename Op>
+ArrayKernelExec ShiftExecFromOp(detail::GetTypeId get_id) {
+ switch (get_id.id) {
+ case Type::INT8:
+ return KernelGenerator::Exec;
+ case Type::UINT8:
+ return KernelGenerator::Exec;
+ case Type::INT16:
+ return KernelGenerator::Exec;
+ case Type::UINT16:
+ return KernelGenerator::Exec;
+ case Type::INT32:
+ return KernelGenerator::Exec;
+ case Type::UINT32:
+ return KernelGenerator::Exec;
+ case Type::INT64:
+ return KernelGenerator::Exec;
+ case Type::UINT64:
+ return KernelGenerator::Exec;
+ default:
+ DCHECK(false);
+ return ExecFail;
+ }
+}
+
Status CastBinaryDecimalArgs(const std::string& func_name,
std::vector* values) {
auto& left_type = (*values)[0].type;
@@ -734,6 +890,28 @@ std::shared_ptr MakeUnarySignedArithmeticFunctionNotNull(
return func;
}
+template
+std::shared_ptr MakeBitWiseFunctionNotNull(std::string name,
+ const FunctionDoc* doc) {
+ auto func = std::make_shared(name, Arity::Binary(), doc);
+ for (const auto& ty : IntTypes()) {
+ auto exec = TypeAgnosticBitWiseExecFromOp(ty);
+ DCHECK_OK(func->AddKernel({ty, ty}, ty, exec));
+ }
+ return func;
+}
+
+template
+std::shared_ptr MakeShiftFunctionNotNull(std::string name,
+ const FunctionDoc* doc) {
+ auto func = std::make_shared(name, Arity::Binary(), doc);
+ for (const auto& ty : IntTypes()) {
+ auto exec = ShiftExecFromOp(ty);
+ DCHECK_OK(func->AddKernel({ty, ty}, ty, exec));
+ }
+ return func;
+}
+
const FunctionDoc absolute_value_doc{
"Calculate the absolute value of the argument element-wise",
("Results will wrap around on integer overflow.\n"
@@ -820,6 +998,58 @@ const FunctionDoc pow_checked_doc{
("An error is returned when integer to negative integer power is encountered,\n"
"or integer overflow is encountered."),
{"base", "exponent"}};
+
+const FunctionDoc bit_wise_not_doc{
+ "Bit-wise negate the arguments element-wise", ("This function never fails."), {"x"}};
+
+const FunctionDoc bit_wise_and_doc{"Bit-wise AND the arguments element-wise",
+ ("This function never fails."),
+ {"x", "y"}};
+
+const FunctionDoc bit_wise_or_doc{
+ "Bit-wise OR the arguments element-wise", ("This function never fails."), {"x", "y"}};
+
+const FunctionDoc bit_wise_xor_doc{"Bit-wise XOR the arguments element-wise",
+ ("This function never fails."),
+ {"x", "y"}};
+
+const FunctionDoc shift_left_doc{
+ "Left shift `x` by `y`",
+ ("This function will return `x` if `y` (the amount to shift by) is: "
+ "(1) negative or (2) greater than or equal to the precision of `x`.\n"
+ "The shift operates as if on the two's complement representation of the number. "
+ "In other words, this is equivalent to multiplying `x` by 2 to the power `y`, "
+ "even if overflow occurs.\n"
+ "Use function \"shift_left_checked\" if you want an invalid shift amount to "
+ "return an error."),
+ {"x", "y"}};
+
+const FunctionDoc shift_left_checked_doc{
+ "Left shift `x` by `y` with invalid shift check",
+ ("This function will raise an error if `y` (the amount to shift by) is: "
+ "(1) negative or (2) greater than or equal to the precision of `x`. "
+ "The shift operates as if on the two's complement representation of the number. "
+ "In other words, this is equivalent to multiplying `x` by 2 to the power `y`, "
+ "even if overflow occurs.\n"
+ "See \"shift_left\" for a variant that doesn't fail for an invalid shift amount."),
+ {"x", "y"}};
+
+const FunctionDoc shift_right_doc{
+ "Right shift `x` by `y`",
+ ("Performs a logical shift for unsigned `x` and an arithmetic shift for signed `x`.\n"
+ "This function will return `x` if `y` (the amount to shift by) is: "
+ "(1) negative or (2) greater than or equal to the precision of `x`.\n"
+ "Use function \"shift_right_checked\" if you want an invalid shift amount to return "
+ "an error."),
+ {"x", "y"}};
+
+const FunctionDoc shift_right_checked_doc{
+ "Right shift `x` by `y` with invalid shift check",
+ ("Performs a logical shift for unsigned `x` and an arithmetic shift for signed `x`.\n"
+ "This function will raise an error if `y` (the amount to shift by) is: "
+ "(1) negative or (2) greater than or equal to the precision of `x`.\n"
+ "See \"shift_right\" for a variant that doesn't fail for an invalid shift amount"),
+ {"x", "y"}};
} // namespace
void RegisterScalarArithmetic(FunctionRegistry* registry) {
@@ -903,6 +1133,44 @@ void RegisterScalarArithmetic(FunctionRegistry* registry) {
auto power_checked =
MakeArithmeticFunctionNotNull("power_checked", &pow_checked_doc);
DCHECK_OK(registry->AddFunction(std::move(power_checked)));
+
+ // ----------------------------------------------------------------------
+ {
+ auto bit_wise_not = std::make_shared(
+ "bit_wise_not", Arity::Unary(), &bit_wise_not_doc);
+ for (const auto& ty : IntTypes()) {
+ auto exec = TypeAgnosticBitWiseExecFromOp(ty);
+ DCHECK_OK(bit_wise_not->AddKernel({ty}, ty, exec));
+ }
+ DCHECK_OK(registry->AddFunction(std::move(bit_wise_not)));
+ }
+
+ auto bit_wise_and =
+ MakeBitWiseFunctionNotNull("bit_wise_and", &bit_wise_and_doc);
+ DCHECK_OK(registry->AddFunction(std::move(bit_wise_and)));
+
+ auto bit_wise_or =
+ MakeBitWiseFunctionNotNull("bit_wise_or", &bit_wise_or_doc);
+ DCHECK_OK(registry->AddFunction(std::move(bit_wise_or)));
+
+ auto bit_wise_xor =
+ MakeBitWiseFunctionNotNull("bit_wise_xor", &bit_wise_xor_doc);
+ DCHECK_OK(registry->AddFunction(std::move(bit_wise_xor)));
+
+ auto shift_left = MakeShiftFunctionNotNull("shift_left", &shift_left_doc);
+ DCHECK_OK(registry->AddFunction(std::move(shift_left)));
+
+ auto shift_left_checked = MakeShiftFunctionNotNull(
+ "shift_left_checked", &shift_left_checked_doc);
+ DCHECK_OK(registry->AddFunction(std::move(shift_left_checked)));
+
+ auto shift_right =
+ MakeShiftFunctionNotNull("shift_right", &shift_right_doc);
+ DCHECK_OK(registry->AddFunction(std::move(shift_right)));
+
+ auto shift_right_checked = MakeShiftFunctionNotNull(
+ "shift_right_checked", &shift_right_checked_doc);
+ DCHECK_OK(registry->AddFunction(std::move(shift_right_checked)));
}
} // namespace internal
diff --git a/cpp/src/arrow/compute/kernels/scalar_arithmetic_test.cc b/cpp/src/arrow/compute/kernels/scalar_arithmetic_test.cc
index ae2f55c6be6..0b809dac6e7 100644
--- a/cpp/src/arrow/compute/kernels/scalar_arithmetic_test.cc
+++ b/cpp/src/arrow/compute/kernels/scalar_arithmetic_test.cc
@@ -299,6 +299,66 @@ class TestBinaryArithmeticUnsigned : public TestBinaryArithmeticIntegral {};
template
class TestBinaryArithmeticFloating : public TestBinaryArithmetic {};
+template
+class TestBitWiseArithmetic : public TestBase {
+ protected:
+ using ArrowType = T;
+ using CType = typename ArrowType::c_type;
+
+ static std::shared_ptr type_singleton() {
+ return TypeTraits::type_singleton();
+ }
+
+ void AssertUnaryOp(const std::string& func, const std::vector& args,
+ const std::vector& expected) {
+ auto input = ExpandByteArray(args);
+ auto output = ExpandByteArray(expected);
+ ASSERT_OK_AND_ASSIGN(Datum actual, CallFunction(func, {input}));
+ ValidateAndAssertEqual(actual.make_array(), output);
+ for (int64_t i = 0; i < output->length(); i++) {
+ ASSERT_OK_AND_ASSIGN(Datum actual, CallFunction(func, {*input->GetScalar(i)}));
+ const auto expected_scalar = *output->GetScalar(i);
+ AssertScalarsEqual(*expected_scalar, *actual.scalar(), /*verbose=*/true);
+ }
+ }
+
+ void AssertBinaryOp(const std::string& func, const std::vector& arg0,
+ const std::vector& arg1,
+ const std::vector& expected) {
+ auto input0 = ExpandByteArray(arg0);
+ auto input1 = ExpandByteArray(arg1);
+ auto output = ExpandByteArray(expected);
+ ASSERT_OK_AND_ASSIGN(Datum actual, CallFunction(func, {input0, input1}));
+ ValidateAndAssertEqual(actual.make_array(), output);
+ for (int64_t i = 0; i < output->length(); i++) {
+ ASSERT_OK_AND_ASSIGN(Datum actual, CallFunction(func, {*input0->GetScalar(i),
+ *input1->GetScalar(i)}));
+ const auto expected_scalar = *output->GetScalar(i);
+ AssertScalarsEqual(*expected_scalar, *actual.scalar(), /*verbose=*/true);
+ }
+ }
+
+ // To make it easier to test different widths, tests give bytes which get repeated to
+ // make an array of the actual type
+ std::shared_ptr ExpandByteArray(const std::vector& values) {
+ std::vector c_values(values.size() + 1);
+ for (size_t i = 0; i < values.size(); i++) {
+ std::memset(&c_values[i], values[i], sizeof(CType));
+ }
+ std::vector valid(values.size() + 1, true);
+ valid.back() = false;
+ std::shared_ptr arr;
+ ArrayFromVector(valid, c_values, &arr);
+ return arr;
+ }
+
+ void ValidateAndAssertEqual(const std::shared_ptr& actual,
+ const std::shared_ptr& expected) {
+ ASSERT_OK(actual->ValidateFull());
+ AssertArraysEqual(*expected, *actual, /*verbose=*/true);
+ }
+};
+
// InputType - OutputType pairs
using IntegralTypes = testing::Types;
@@ -321,6 +381,31 @@ TYPED_TEST_SUITE(TestBinaryArithmeticSigned, SignedIntegerTypes);
TYPED_TEST_SUITE(TestBinaryArithmeticUnsigned, UnsignedIntegerTypes);
TYPED_TEST_SUITE(TestBinaryArithmeticFloating, FloatingTypes);
+TYPED_TEST_SUITE(TestBitWiseArithmetic, IntegralTypes);
+
+TYPED_TEST(TestBitWiseArithmetic, BitWiseNot) {
+ this->AssertUnaryOp("bit_wise_not", std::vector{0x00, 0x55, 0xAA, 0xFF},
+ std::vector{0xFF, 0xAA, 0x55, 0x00});
+}
+
+TYPED_TEST(TestBitWiseArithmetic, BitWiseAnd) {
+ this->AssertBinaryOp("bit_wise_and", std::vector{0x00, 0xFF, 0x00, 0xFF},
+ std::vector{0x00, 0x00, 0xFF, 0xFF},
+ std::vector{0x00, 0x00, 0x00, 0xFF});
+}
+
+TYPED_TEST(TestBitWiseArithmetic, BitWiseOr) {
+ this->AssertBinaryOp("bit_wise_or", std::vector{0x00, 0xFF, 0x00, 0xFF},
+ std::vector{0x00, 0x00, 0xFF, 0xFF},
+ std::vector{0x00, 0xFF, 0xFF, 0xFF});
+}
+
+TYPED_TEST(TestBitWiseArithmetic, BitWiseXor) {
+ this->AssertBinaryOp("bit_wise_xor", std::vector{0x00, 0xFF, 0x00, 0xFF},
+ std::vector{0x00, 0x00, 0xFF, 0xFF},
+ std::vector{0x00, 0xFF, 0xFF, 0x00});
+}
+
TYPED_TEST(TestBinaryArithmeticIntegral, Add) {
for (auto check_overflow : {false, true}) {
this->SetOverflowCheck(check_overflow);
@@ -1511,5 +1596,121 @@ TEST(TestBinaryArithmeticDecimal, Divide) {
}
}
+TYPED_TEST(TestBinaryArithmeticIntegral, ShiftLeft) {
+ for (auto check_overflow : {false, true}) {
+ this->SetOverflowCheck(check_overflow);
+
+ this->AssertBinop(ShiftLeft, "[]", "[]", "[]");
+ this->AssertBinop(ShiftLeft, "[0, 1, 2, 3]", "[2, 3, 4, 5]", "[0, 8, 32, 96]");
+ // Nulls on one side
+ this->AssertBinop(ShiftLeft, "[0, null, 2, 3]", "[2, 3, 4, 5]", "[0, null, 32, 96]");
+ this->AssertBinop(ShiftLeft, "[0, 1, 2, 3]", "[2, 3, null, 5]", "[0, 8, null, 96]");
+ // Nulls on both sides
+ this->AssertBinop(ShiftLeft, "[0, null, 2, 3]", "[2, 3, null, 5]",
+ "[0, null, null, 96]");
+ // All nulls
+ this->AssertBinop(ShiftLeft, "[null]", "[null]", "[null]");
+
+ // Scalar on the left
+ this->AssertBinop(ShiftLeft, 2, "[null, 5]", "[null, 64]");
+ this->AssertBinop(ShiftLeft, this->MakeNullScalar(), "[null, 5]", "[null, null]");
+ // Scalar on the right
+ this->AssertBinop(ShiftLeft, "[null, 5]", 3, "[null, 40]");
+ this->AssertBinop(ShiftLeft, "[null, 5]", this->MakeNullScalar(), "[null, null]");
+ }
+}
+
+TYPED_TEST(TestBinaryArithmeticIntegral, ShiftRight) {
+ for (auto check_overflow : {false, true}) {
+ this->SetOverflowCheck(check_overflow);
+
+ this->AssertBinop(ShiftRight, "[]", "[]", "[]");
+ this->AssertBinop(ShiftRight, "[0, 1, 4, 8]", "[1, 1, 1, 4]", "[0, 0, 2, 0]");
+ // Nulls on one side
+ this->AssertBinop(ShiftRight, "[0, null, 4, 8]", "[1, 1, 1, 4]", "[0, null, 2, 0]");
+ this->AssertBinop(ShiftRight, "[0, 1, 4, 8]", "[1, 1, null, 4]", "[0, 0, null, 0]");
+ // Nulls on both sides
+ this->AssertBinop(ShiftRight, "[0, null, 4, 8]", "[1, 1, null, 4]",
+ "[0, null, null, 0]");
+ // All nulls
+ this->AssertBinop(ShiftRight, "[null]", "[null]", "[null]");
+
+ // Scalar on the left
+ this->AssertBinop(ShiftRight, 64, "[null, 2, 6]", "[null, 16, 1]");
+ this->AssertBinop(ShiftRight, this->MakeNullScalar(), "[null, 2, 6]",
+ "[null, null, null]");
+ // Scalar on the right
+ this->AssertBinop(ShiftRight, "[null, 3, 96]", 3, "[null, 0, 12]");
+ this->AssertBinop(ShiftRight, "[null, 3, 96]", this->MakeNullScalar(),
+ "[null, null, null]");
+ }
+}
+
+TYPED_TEST(TestBinaryArithmeticSigned, ShiftLeftOverflowRaises) {
+ using CType = typename TestFixture::CType;
+ const CType bit_width = static_cast(std::numeric_limits::digits);
+ const CType min = std::numeric_limits::min();
+ this->SetOverflowCheck(true);
+
+ this->AssertBinop(ShiftLeft, "[1]", MakeArray(bit_width - 1),
+ MakeArray(static_cast(1) << (bit_width - 1)));
+ this->AssertBinop(ShiftLeft, "[2]", MakeArray(bit_width - 2),
+ MakeArray(static_cast(1) << (bit_width - 1)));
+ // Shift a bit into the sign bit
+ this->AssertBinop(ShiftLeft, "[2]", MakeArray(bit_width - 1), MakeArray(min));
+ // Shift a bit past the sign bit
+ this->AssertBinop(ShiftLeft, "[4]", MakeArray(bit_width - 1), "[0]");
+ this->AssertBinop(ShiftLeft, MakeArray(min), "[1]", "[0]");
+ this->AssertBinopRaises(ShiftLeft, "[1, 2]", "[1, -1]", "shift must be non-negative");
+ this->AssertBinopRaises(ShiftLeft, "[1]", MakeArray(bit_width), "overflow");
+
+ this->SetOverflowCheck(false);
+ this->AssertBinop(ShiftLeft, "[1, 1]", MakeArray(-1, bit_width), "[1, 1]");
+}
+
+TYPED_TEST(TestBinaryArithmeticSigned, ShiftRightOverflowRaises) {
+ using CType = typename TestFixture::CType;
+ const CType bit_width = static_cast(std::numeric_limits::digits);
+ const CType max = std::numeric_limits::max();
+ const CType min = std::numeric_limits::min();
+ this->SetOverflowCheck(true);
+
+ this->AssertBinop(ShiftRight, MakeArray(max), MakeArray(bit_width - 1), "[1]");
+ this->AssertBinop(ShiftRight, "[-1, -1]", "[1, 5]", "[-1, -1]");
+ this->AssertBinop(ShiftRight, MakeArray(min), "[1]", MakeArray(min / 2));
+ this->AssertBinopRaises(ShiftRight, "[1, 2]", "[1, -1]",
+ "rhs must be >= 0 and less than precision of type");
+ this->AssertBinopRaises(ShiftRight, "[1]", MakeArray(bit_width),
+ "rhs must be >= 0 and less than precision of type");
+
+ this->SetOverflowCheck(false);
+ this->AssertBinop(ShiftRight, "[1, 1]", MakeArray(-1, bit_width), "[1, 1]");
+}
+
+TYPED_TEST(TestBinaryArithmeticUnsigned, ShiftLeftOverflowRaises) {
+ using CType = typename TestFixture::CType;
+ const CType bit_width = static_cast(std::numeric_limits::digits);
+ this->SetOverflowCheck(true);
+
+ this->AssertBinop(ShiftLeft, "[1]", MakeArray(bit_width - 1),
+ MakeArray(static_cast(1) << (bit_width - 1)));
+ this->AssertBinop(ShiftLeft, "[2]", MakeArray(bit_width - 2),
+ MakeArray(static_cast(1) << (bit_width - 1)));
+ this->AssertBinop(ShiftLeft, "[2]", MakeArray(bit_width - 1), "[0]");
+ this->AssertBinop(ShiftLeft, "[4]", MakeArray(bit_width - 1), "[0]");
+ this->AssertBinopRaises(ShiftLeft, "[1]", MakeArray(bit_width), "overflow");
+}
+
+TYPED_TEST(TestBinaryArithmeticUnsigned, ShiftRightOverflowRaises) {
+ using CType = typename TestFixture::CType;
+ const CType bit_width = static_cast(std::numeric_limits::digits);
+ const CType max = std::numeric_limits::max();
+ this->SetOverflowCheck(true);
+
+ this->AssertBinop(ShiftRight, MakeArray(max), MakeArray(bit_width - 1), "[1]");
+ this->AssertBinopRaises(ShiftRight, "[1]", MakeArray(bit_width),
+ "rhs must be >= 0 and less than precision of type");
+}
+
} // namespace compute
} // namespace arrow
diff --git a/docs/source/cpp/compute.rst b/docs/source/cpp/compute.rst
index e785756dcda..f9b657023d5 100644
--- a/docs/source/cpp/compute.rst
+++ b/docs/source/cpp/compute.rst
@@ -286,6 +286,14 @@ an ``Invalid`` :class:`Status` when overflow is detected.
+--------------------------+------------+--------------------+---------------------+
| power_checked | Binary | Numeric | Numeric |
+--------------------------+------------+--------------------+---------------------+
+| shift_left | Binary | Numeric | Numeric |
++--------------------------+------------+--------------------+---------------------+
+| shift_left_checked | Binary | Numeric | Numeric |
++--------------------------+------------+--------------------+---------------------+
+| shift_right | Binary | Numeric | Numeric |
++--------------------------+------------+--------------------+---------------------+
+| shift_right_checked | Binary | Numeric | Numeric |
++--------------------------+------------+--------------------+---------------------+
| subtract | Binary | Numeric | Numeric (1) |
+--------------------------+------------+--------------------+---------------------+
| subtract_checked | Binary | Numeric | Numeric (1) |
@@ -312,6 +320,21 @@ precision of `divide` is at least the sum of precisions of both operands with
enough scale kept. Error is returned if the result precision is beyond the
decimal value range.
+Bit-wise functions are similar, except there is no need for an
+overflow-checking variant.
+
++--------------------------+------------+--------------------+---------------------+
+| Function name | Arity | Input types | Output type |
++==========================+============+====================+=====================+
+| bit_wise_and | Binary | Numeric | Numeric |
++--------------------------+------------+--------------------+---------------------+
+| bit_wise_not | Unary | Numeric | Numeric |
++--------------------------+------------+--------------------+---------------------+
+| bit_wise_or | Binary | Numeric | Numeric |
++--------------------------+------------+--------------------+---------------------+
+| bit_wise_xor | Binary | Numeric | Numeric |
++--------------------------+------------+--------------------+---------------------+
+
Comparisons
~~~~~~~~~~~
diff --git a/docs/source/python/api/compute.rst b/docs/source/python/api/compute.rst
index 8b264ed9b83..e0caba59b53 100644
--- a/docs/source/python/api/compute.rst
+++ b/docs/source/python/api/compute.rst
@@ -58,6 +58,20 @@ throws an ``ArrowInvalid`` exception when overflow is detected.
subtract_checked
power
power_checked
+ shift_left
+ shift_left_checked
+ shift_right
+ shift_right_checked
+
+Bit-wise operations do not offer (or need) a checked variant.
+
+.. autosummary::
+ :toctree: ../generated/
+
+ bit_wise_and
+ bit_wise_not
+ bit_wise_or
+ bit_wise_xor
Comparisons
-----------
From 4b14a56f1a1d0a4fc00e01506b23f155b91aec09 Mon Sep 17 00:00:00 2001
From: David Li
Date: Wed, 23 Jun 2021 08:13:52 -0400
Subject: [PATCH 2/6] ARROW-13072: [C++] Address review feedback
---
.../compute/kernels/scalar_arithmetic.cc | 30 +++++++------------
.../compute/kernels/scalar_arithmetic_test.cc | 6 ++--
2 files changed, 14 insertions(+), 22 deletions(-)
diff --git a/cpp/src/arrow/compute/kernels/scalar_arithmetic.cc b/cpp/src/arrow/compute/kernels/scalar_arithmetic.cc
index 8a1e5639a7a..74ff5f60fd0 100644
--- a/cpp/src/arrow/compute/kernels/scalar_arithmetic.cc
+++ b/cpp/src/arrow/compute/kernels/scalar_arithmetic.cc
@@ -502,12 +502,8 @@ struct ShiftLeftChecked {
static enable_if_unsigned_integer Call(KernelContext*, Arg0 lhs, Arg1 rhs,
Status* st) {
static_assert(std::is_same::value, "");
- if (ARROW_PREDICT_FALSE(rhs < 0)) {
- *st = Status::Invalid("shift must be non-negative");
- return lhs;
- }
- if (ARROW_PREDICT_FALSE(rhs >= std::numeric_limits::digits)) {
- *st = Status::Invalid("overflow");
+ if (ARROW_PREDICT_FALSE(rhs < 0 || std::numeric_limits::digits)) {
+ *st = Status::Invalid("rhs must be >= 0 and less than precision of type");
return lhs;
}
return lhs << rhs;
@@ -518,12 +514,8 @@ struct ShiftLeftChecked {
Status* st) {
using Unsigned = typename std::make_unsigned::type;
static_assert(std::is_same::value, "");
- if (ARROW_PREDICT_FALSE(rhs < 0)) {
- *st = Status::Invalid("shift must be non-negative");
- return lhs;
- }
- if (ARROW_PREDICT_FALSE(rhs >= std::numeric_limits::digits)) {
- *st = Status::Invalid("overflow");
+ if (ARROW_PREDICT_FALSE(rhs < 0 || std::numeric_limits::digits)) {
+ *st = Status::Invalid("rhs must be >= 0 and less than precision of type");
return lhs;
}
// In C/C++ left shift of a negative number is undefined (C++11 standard 5.8.2)
@@ -1000,18 +992,16 @@ const FunctionDoc pow_checked_doc{
{"base", "exponent"}};
const FunctionDoc bit_wise_not_doc{
- "Bit-wise negate the arguments element-wise", ("This function never fails."), {"x"}};
+ "Bit-wise negate the arguments element-wise", (""), {"x"}};
-const FunctionDoc bit_wise_and_doc{"Bit-wise AND the arguments element-wise",
- ("This function never fails."),
- {"x", "y"}};
+const FunctionDoc bit_wise_and_doc{
+ "Bit-wise AND the arguments element-wise", (""), {"x", "y"}};
const FunctionDoc bit_wise_or_doc{
- "Bit-wise OR the arguments element-wise", ("This function never fails."), {"x", "y"}};
+ "Bit-wise OR the arguments element-wise", (""), {"x", "y"}};
-const FunctionDoc bit_wise_xor_doc{"Bit-wise XOR the arguments element-wise",
- ("This function never fails."),
- {"x", "y"}};
+const FunctionDoc bit_wise_xor_doc{
+ "Bit-wise XOR the arguments element-wise", (""), {"x", "y"}};
const FunctionDoc shift_left_doc{
"Left shift `x` by `y`",
diff --git a/cpp/src/arrow/compute/kernels/scalar_arithmetic_test.cc b/cpp/src/arrow/compute/kernels/scalar_arithmetic_test.cc
index 0b809dac6e7..989bf1ca220 100644
--- a/cpp/src/arrow/compute/kernels/scalar_arithmetic_test.cc
+++ b/cpp/src/arrow/compute/kernels/scalar_arithmetic_test.cc
@@ -1661,8 +1661,10 @@ TYPED_TEST(TestBinaryArithmeticSigned, ShiftLeftOverflowRaises) {
// Shift a bit past the sign bit
this->AssertBinop(ShiftLeft, "[4]", MakeArray(bit_width - 1), "[0]");
this->AssertBinop(ShiftLeft, MakeArray(min), "[1]", "[0]");
- this->AssertBinopRaises(ShiftLeft, "[1, 2]", "[1, -1]", "shift must be non-negative");
- this->AssertBinopRaises(ShiftLeft, "[1]", MakeArray(bit_width), "overflow");
+ this->AssertBinopRaises(ShiftLeft, "[1, 2]", "[1, -1]",
+ "rhs must be >= 0 and less than precision of type");
+ this->AssertBinopRaises(ShiftLeft, "[1]", MakeArray(bit_width),
+ "rhs must be >= 0 and less than precision of type");
this->SetOverflowCheck(false);
this->AssertBinop(ShiftLeft, "[1, 1]", MakeArray(-1, bit_width), "[1, 1]");
From 22e6b290fdd9e664c21c5ed29800f431e91d7471 Mon Sep 17 00:00:00 2001
From: David Li
Date: Wed, 23 Jun 2021 15:18:22 -0400
Subject: [PATCH 3/6] ARROW-13072: [C++] Fix typo
---
cpp/src/arrow/compute/kernels/scalar_arithmetic.cc | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/cpp/src/arrow/compute/kernels/scalar_arithmetic.cc b/cpp/src/arrow/compute/kernels/scalar_arithmetic.cc
index 74ff5f60fd0..ea4fe292b06 100644
--- a/cpp/src/arrow/compute/kernels/scalar_arithmetic.cc
+++ b/cpp/src/arrow/compute/kernels/scalar_arithmetic.cc
@@ -502,7 +502,7 @@ struct ShiftLeftChecked {
static enable_if_unsigned_integer Call(KernelContext*, Arg0 lhs, Arg1 rhs,
Status* st) {
static_assert(std::is_same::value, "");
- if (ARROW_PREDICT_FALSE(rhs < 0 || std::numeric_limits::digits)) {
+ if (ARROW_PREDICT_FALSE(rhs < 0 || rhs >= std::numeric_limits::digits)) {
*st = Status::Invalid("rhs must be >= 0 and less than precision of type");
return lhs;
}
@@ -514,7 +514,7 @@ struct ShiftLeftChecked {
Status* st) {
using Unsigned = typename std::make_unsigned::type;
static_assert(std::is_same::value, "");
- if (ARROW_PREDICT_FALSE(rhs < 0 || std::numeric_limits::digits)) {
+ if (ARROW_PREDICT_FALSE(rhs < 0 || rhs >= std::numeric_limits::digits)) {
*st = Status::Invalid("rhs must be >= 0 and less than precision of type");
return lhs;
}
From 9e9bde60c0e13b25a6c1f70b89bba7acd8cf0914 Mon Sep 17 00:00:00 2001
From: David Li
Date: Wed, 23 Jun 2021 16:03:00 -0400
Subject: [PATCH 4/6] ARROW-13072: [C++] Fix other typo
---
cpp/src/arrow/compute/kernels/scalar_arithmetic_test.cc | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/cpp/src/arrow/compute/kernels/scalar_arithmetic_test.cc b/cpp/src/arrow/compute/kernels/scalar_arithmetic_test.cc
index 989bf1ca220..57ea7f92531 100644
--- a/cpp/src/arrow/compute/kernels/scalar_arithmetic_test.cc
+++ b/cpp/src/arrow/compute/kernels/scalar_arithmetic_test.cc
@@ -1700,7 +1700,8 @@ TYPED_TEST(TestBinaryArithmeticUnsigned, ShiftLeftOverflowRaises) {
MakeArray(static_cast(1) << (bit_width - 1)));
this->AssertBinop(ShiftLeft, "[2]", MakeArray(bit_width - 1), "[0]");
this->AssertBinop(ShiftLeft, "[4]", MakeArray(bit_width - 1), "[0]");
- this->AssertBinopRaises(ShiftLeft, "[1]", MakeArray(bit_width), "overflow");
+ this->AssertBinopRaises(ShiftLeft, "[1]", MakeArray(bit_width),
+ "rhs must be >= 0 and less than precision of type");
}
TYPED_TEST(TestBinaryArithmeticUnsigned, ShiftRightOverflowRaises) {
From c72e55368677d98ef089034369f81595fe94d4e9 Mon Sep 17 00:00:00 2001
From: David Li
Date: Thu, 24 Jun 2021 08:38:14 -0400
Subject: [PATCH 5/6] ARROW-13072: [C++] Tweak docstring
---
cpp/src/arrow/compute/kernels/scalar_arithmetic.cc | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/cpp/src/arrow/compute/kernels/scalar_arithmetic.cc b/cpp/src/arrow/compute/kernels/scalar_arithmetic.cc
index ea4fe292b06..7cc8d0c434c 100644
--- a/cpp/src/arrow/compute/kernels/scalar_arithmetic.cc
+++ b/cpp/src/arrow/compute/kernels/scalar_arithmetic.cc
@@ -992,16 +992,16 @@ const FunctionDoc pow_checked_doc{
{"base", "exponent"}};
const FunctionDoc bit_wise_not_doc{
- "Bit-wise negate the arguments element-wise", (""), {"x"}};
+ "Bit-wise negate the arguments element-wise", ("Null values return null."), {"x"}};
const FunctionDoc bit_wise_and_doc{
- "Bit-wise AND the arguments element-wise", (""), {"x", "y"}};
+ "Bit-wise AND the arguments element-wise", ("Null values return null."), {"x", "y"}};
const FunctionDoc bit_wise_or_doc{
- "Bit-wise OR the arguments element-wise", (""), {"x", "y"}};
+ "Bit-wise OR the arguments element-wise", ("Null values return null."), {"x", "y"}};
const FunctionDoc bit_wise_xor_doc{
- "Bit-wise XOR the arguments element-wise", (""), {"x", "y"}};
+ "Bit-wise XOR the arguments element-wise", ("Null values return null."), {"x", "y"}};
const FunctionDoc shift_left_doc{
"Left shift `x` by `y`",
From ebfb20054a1e83cb734370035865cc71ac579d6b Mon Sep 17 00:00:00 2001
From: Antoine Pitrou
Date: Mon, 28 Jun 2021 17:18:50 +0200
Subject: [PATCH 6/6] Nits in docs
---
.../compute/kernels/scalar_arithmetic.cc | 11 ++--
.../compute/kernels/scalar_arithmetic_test.cc | 12 ++--
docs/source/cpp/compute.rst | 62 ++++++++++---------
3 files changed, 45 insertions(+), 40 deletions(-)
diff --git a/cpp/src/arrow/compute/kernels/scalar_arithmetic.cc b/cpp/src/arrow/compute/kernels/scalar_arithmetic.cc
index 7cc8d0c434c..ef9ef78054a 100644
--- a/cpp/src/arrow/compute/kernels/scalar_arithmetic.cc
+++ b/cpp/src/arrow/compute/kernels/scalar_arithmetic.cc
@@ -503,7 +503,7 @@ struct ShiftLeftChecked {
Status* st) {
static_assert(std::is_same::value, "");
if (ARROW_PREDICT_FALSE(rhs < 0 || rhs >= std::numeric_limits::digits)) {
- *st = Status::Invalid("rhs must be >= 0 and less than precision of type");
+ *st = Status::Invalid("shift amount must be >= 0 and less than precision of type");
return lhs;
}
return lhs << rhs;
@@ -515,7 +515,7 @@ struct ShiftLeftChecked {
using Unsigned = typename std::make_unsigned::type;
static_assert(std::is_same::value, "");
if (ARROW_PREDICT_FALSE(rhs < 0 || rhs >= std::numeric_limits::digits)) {
- *st = Status::Invalid("rhs must be >= 0 and less than precision of type");
+ *st = Status::Invalid("shift amount must be >= 0 and less than precision of type");
return lhs;
}
// In C/C++ left shift of a negative number is undefined (C++11 standard 5.8.2)
@@ -547,7 +547,7 @@ struct ShiftRightChecked {
static T Call(KernelContext*, Arg0 lhs, Arg1 rhs, Status* st) {
static_assert(std::is_same::value, "");
if (ARROW_PREDICT_FALSE(rhs < 0 || rhs >= std::numeric_limits::digits)) {
- *st = Status::Invalid("rhs must be >= 0 and less than precision of type");
+ *st = Status::Invalid("shift amount must be >= 0 and less than precision of type");
return lhs;
}
return lhs >> rhs;
@@ -1026,7 +1026,7 @@ const FunctionDoc shift_left_checked_doc{
const FunctionDoc shift_right_doc{
"Right shift `x` by `y`",
- ("Performs a logical shift for unsigned `x` and an arithmetic shift for signed `x`.\n"
+ ("Perform a logical shift for unsigned `x` and an arithmetic shift for signed `x`.\n"
"This function will return `x` if `y` (the amount to shift by) is: "
"(1) negative or (2) greater than or equal to the precision of `x`.\n"
"Use function \"shift_right_checked\" if you want an invalid shift amount to return "
@@ -1035,11 +1035,12 @@ const FunctionDoc shift_right_doc{
const FunctionDoc shift_right_checked_doc{
"Right shift `x` by `y` with invalid shift check",
- ("Performs a logical shift for unsigned `x` and an arithmetic shift for signed `x`.\n"
+ ("Perform a logical shift for unsigned `x` and an arithmetic shift for signed `x`.\n"
"This function will raise an error if `y` (the amount to shift by) is: "
"(1) negative or (2) greater than or equal to the precision of `x`.\n"
"See \"shift_right\" for a variant that doesn't fail for an invalid shift amount"),
{"x", "y"}};
+
} // namespace
void RegisterScalarArithmetic(FunctionRegistry* registry) {
diff --git a/cpp/src/arrow/compute/kernels/scalar_arithmetic_test.cc b/cpp/src/arrow/compute/kernels/scalar_arithmetic_test.cc
index 57ea7f92531..a94eabb1be0 100644
--- a/cpp/src/arrow/compute/kernels/scalar_arithmetic_test.cc
+++ b/cpp/src/arrow/compute/kernels/scalar_arithmetic_test.cc
@@ -1662,9 +1662,9 @@ TYPED_TEST(TestBinaryArithmeticSigned, ShiftLeftOverflowRaises) {
this->AssertBinop(ShiftLeft, "[4]", MakeArray(bit_width - 1), "[0]");
this->AssertBinop(ShiftLeft, MakeArray(min), "[1]", "[0]");
this->AssertBinopRaises(ShiftLeft, "[1, 2]", "[1, -1]",
- "rhs must be >= 0 and less than precision of type");
+ "shift amount must be >= 0 and less than precision of type");
this->AssertBinopRaises(ShiftLeft, "[1]", MakeArray(bit_width),
- "rhs must be >= 0 and less than precision of type");
+ "shift amount must be >= 0 and less than precision of type");
this->SetOverflowCheck(false);
this->AssertBinop(ShiftLeft, "[1, 1]", MakeArray(-1, bit_width), "[1, 1]");
@@ -1681,9 +1681,9 @@ TYPED_TEST(TestBinaryArithmeticSigned, ShiftRightOverflowRaises) {
this->AssertBinop(ShiftRight, "[-1, -1]", "[1, 5]", "[-1, -1]");
this->AssertBinop(ShiftRight, MakeArray(min), "[1]", MakeArray(min / 2));
this->AssertBinopRaises(ShiftRight, "[1, 2]", "[1, -1]",
- "rhs must be >= 0 and less than precision of type");
+ "shift amount must be >= 0 and less than precision of type");
this->AssertBinopRaises(ShiftRight, "[1]", MakeArray(bit_width),
- "rhs must be >= 0 and less than precision of type");
+ "shift amount must be >= 0 and less than precision of type");
this->SetOverflowCheck(false);
this->AssertBinop(ShiftRight, "[1, 1]", MakeArray(-1, bit_width), "[1, 1]");
@@ -1701,7 +1701,7 @@ TYPED_TEST(TestBinaryArithmeticUnsigned, ShiftLeftOverflowRaises) {
this->AssertBinop(ShiftLeft, "[2]", MakeArray(bit_width - 1), "[0]");
this->AssertBinop(ShiftLeft, "[4]", MakeArray(bit_width - 1), "[0]");
this->AssertBinopRaises(ShiftLeft, "[1]", MakeArray(bit_width),
- "rhs must be >= 0 and less than precision of type");
+ "shift amount must be >= 0 and less than precision of type");
}
TYPED_TEST(TestBinaryArithmeticUnsigned, ShiftRightOverflowRaises) {
@@ -1712,7 +1712,7 @@ TYPED_TEST(TestBinaryArithmeticUnsigned, ShiftRightOverflowRaises) {
this->AssertBinop(ShiftRight, MakeArray(max), MakeArray(bit_width - 1), "[1]");
this->AssertBinopRaises(ShiftRight, "[1]", MakeArray(bit_width),
- "rhs must be >= 0 and less than precision of type");
+ "shift amount must be >= 0 and less than precision of type");
}
} // namespace compute
diff --git a/docs/source/cpp/compute.rst b/docs/source/cpp/compute.rst
index f9b657023d5..c946cd4f330 100644
--- a/docs/source/cpp/compute.rst
+++ b/docs/source/cpp/compute.rst
@@ -286,14 +286,6 @@ an ``Invalid`` :class:`Status` when overflow is detected.
+--------------------------+------------+--------------------+---------------------+
| power_checked | Binary | Numeric | Numeric |
+--------------------------+------------+--------------------+---------------------+
-| shift_left | Binary | Numeric | Numeric |
-+--------------------------+------------+--------------------+---------------------+
-| shift_left_checked | Binary | Numeric | Numeric |
-+--------------------------+------------+--------------------+---------------------+
-| shift_right | Binary | Numeric | Numeric |
-+--------------------------+------------+--------------------+---------------------+
-| shift_right_checked | Binary | Numeric | Numeric |
-+--------------------------+------------+--------------------+---------------------+
| subtract | Binary | Numeric | Numeric (1) |
+--------------------------+------------+--------------------+---------------------+
| subtract_checked | Binary | Numeric | Numeric (1) |
@@ -301,27 +293,27 @@ an ``Invalid`` :class:`Status` when overflow is detected.
* \(1) Precision and scale of computed DECIMAL results
-+------------+---------------------------------------------+
-| Operation | Result precision and scale |
-+============+=============================================+
-| | add | | scale = max(s1, s2) |
-| | subtract | | precision = max(p1-s1, p2-s2) + 1 + scale |
-+------------+---------------------------------------------+
-| multiply | | scale = s1 + s2 |
-| | | precision = p1 + p2 + 1 |
-+------------+---------------------------------------------+
-| divide | | scale = max(4, s1 + p2 - s2 + 1) |
-| | | precision = p1 - s1 + s2 + scale |
-+------------+---------------------------------------------+
-
-It's compatible with Redshift's decimal promotion rules. All decimal digits
-are preserved for `add`, `subtract` and `multiply` operations. The result
-precision of `divide` is at least the sum of precisions of both operands with
-enough scale kept. Error is returned if the result precision is beyond the
-decimal value range.
-
-Bit-wise functions are similar, except there is no need for an
-overflow-checking variant.
+ +------------+---------------------------------------------+
+ | Operation | Result precision and scale |
+ +============+=============================================+
+ | | add | | scale = max(s1, s2) |
+ | | subtract | | precision = max(p1-s1, p2-s2) + 1 + scale |
+ +------------+---------------------------------------------+
+ | multiply | | scale = s1 + s2 |
+ | | | precision = p1 + p2 + 1 |
+ +------------+---------------------------------------------+
+ | divide | | scale = max(4, s1 + p2 - s2 + 1) |
+ | | | precision = p1 - s1 + s2 + scale |
+ +------------+---------------------------------------------+
+
+ It's compatible with Redshift's decimal promotion rules. All decimal digits
+ are preserved for `add`, `subtract` and `multiply` operations. The result
+ precision of `divide` is at least the sum of precisions of both operands with
+ enough scale kept. Error is returned if the result precision is beyond the
+ decimal value range.
+
+Bit-wise functions
+~~~~~~~~~~~~~~~~~~
+--------------------------+------------+--------------------+---------------------+
| Function name | Arity | Input types | Output type |
@@ -334,6 +326,18 @@ overflow-checking variant.
+--------------------------+------------+--------------------+---------------------+
| bit_wise_xor | Binary | Numeric | Numeric |
+--------------------------+------------+--------------------+---------------------+
+| shift_left | Binary | Numeric | Numeric |
++--------------------------+------------+--------------------+---------------------+
+| shift_left_checked | Binary | Numeric | Numeric (1) |
++--------------------------+------------+--------------------+---------------------+
+| shift_right | Binary | Numeric | Numeric |
++--------------------------+------------+--------------------+---------------------+
+| shift_right_checked | Binary | Numeric | Numeric (1) |
++--------------------------+------------+--------------------+---------------------+
+
+* \(1) An error is emitted if the shift amount (i.e. the second input) is
+ out of bounds for the data type. However, an overflow when shifting the
+ first input is not error (truncated bits are silently discarded).
Comparisons
~~~~~~~~~~~