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