diff --git a/cpp/src/arrow/compute/api_scalar.cc b/cpp/src/arrow/compute/api_scalar.cc index db1cac290cf96..f005e70e3480c 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 5c83dcb5c851d..b101325740194 100644 --- a/cpp/src/arrow/compute/api_scalar.h +++ b/cpp/src/arrow/compute/api_scalar.h @@ -308,6 +308,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 f51484e53ff8f..ef9ef78054a61 100644 --- a/cpp/src/arrow/compute/kernels/scalar_arithmetic.cc +++ b/cpp/src/arrow/compute/kernels/scalar_arithmetic.cc @@ -454,6 +454,106 @@ 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 || rhs >= std::numeric_limits::digits)) { + *st = Status::Invalid("shift amount must be >= 0 and less than precision of type"); + 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 || rhs >= std::numeric_limits::digits)) { + *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) + // 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("shift amount must be >= 0 and less than precision of type"); + return lhs; + } + return lhs >> rhs; + } +}; + // Generate a kernel given an arithmetic functor template