Skip to content
Closed
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
1 change: 1 addition & 0 deletions cpp/src/arrow/compute/api_scalar.cc
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ namespace compute {
return CallFunction(func_name, {arg}, ctx); \
}

SCALAR_ARITHMETIC_UNARY(AbsoluteValue, "abs", "abs_checked")
SCALAR_ARITHMETIC_UNARY(Negate, "negate", "negate_checked")

#define SCALAR_ARITHMETIC_BINARY(NAME, REGISTRY_NAME, REGISTRY_CHECKED_NAME) \
Expand Down
12 changes: 12 additions & 0 deletions cpp/src/arrow/compute/api_scalar.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,18 @@ struct ARROW_EXPORT ProjectOptions : public FunctionOptions {

/// @}

/// \brief Get the absolute value of a value. Array values can be of arbitrary
/// length. If argument is null the result will be null.
///
/// \param[in] arg the value transformed
/// \param[in] options arithmetic options (overflow handling), optional
/// \param[in] ctx the function execution context, optional
/// \return the elementwise absolute value
ARROW_EXPORT
Result<Datum> AbsoluteValue(const Datum& arg,
ArithmeticOptions options = ArithmeticOptions(),
ExecContext* ctx = NULLPTR);

/// \brief Add two values together. Array values must be the same length. If
/// either addend is null the result will be null.
///
Expand Down
78 changes: 78 additions & 0 deletions cpp/src/arrow/compute/kernels/scalar_arithmetic.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
// under the License.

#include <cmath>
#include <limits>

#include "arrow/compute/kernels/common.h"
#include "arrow/type_traits.h"
Expand Down Expand Up @@ -66,6 +67,47 @@ constexpr Unsigned to_unsigned(T signed_) {
return static_cast<Unsigned>(signed_);
}

struct AbsoluteValue {
template <typename T, typename Arg>
static constexpr enable_if_floating_point<T> Call(KernelContext*, T arg, Status*) {
return std::fabs(arg);
}

template <typename T, typename Arg>
static constexpr enable_if_unsigned_integer<T> Call(KernelContext*, T arg, Status*) {
return arg;
}

template <typename T, typename Arg>
static constexpr enable_if_signed_integer<T> Call(KernelContext*, T arg, Status* st) {
return (arg < 0) ? arrow::internal::SafeSignedNegate(arg) : arg;
}
};

struct AbsoluteValueChecked {
template <typename T, typename Arg>
static enable_if_signed_integer<T> Call(KernelContext*, Arg arg, Status* st) {
static_assert(std::is_same<T, Arg>::value, "");
if (arg == std::numeric_limits<Arg>::min()) {
*st = Status::Invalid("overflow");
return arg;
}
return std::abs(arg);
}

template <typename T, typename Arg>
static enable_if_unsigned_integer<T> Call(KernelContext* ctx, Arg arg, Status* st) {
static_assert(std::is_same<T, Arg>::value, "");
return arg;
}

template <typename T, typename Arg>
static constexpr enable_if_floating_point<T> Call(KernelContext*, Arg arg, Status* st) {
static_assert(std::is_same<T, Arg>::value, "");
return std::fabs(arg);
}
};

struct Add {
template <typename T>
static constexpr enable_if_floating_point<T> Call(KernelContext*, T left, T right,
Expand Down Expand Up @@ -446,6 +488,19 @@ std::shared_ptr<ScalarFunction> MakeUnaryArithmeticFunction(std::string name,
return func;
}

// Like MakeUnaryArithmeticFunction, but for arithmetic ops that need to run
// only on non-null output.
template <typename Op>
std::shared_ptr<ScalarFunction> MakeUnaryArithmeticFunctionNotNull(
std::string name, const FunctionDoc* doc) {
auto func = std::make_shared<ArithmeticFunction>(name, Arity::Unary(), doc);
for (const auto& ty : NumericTypes()) {
auto exec = ArithmeticExecFromOp<ScalarUnaryNotNull, Op>(ty);
DCHECK_OK(func->AddKernel({ty}, ty, exec));
}
return func;
}

// Like MakeUnaryArithmeticFunction, but for signed arithmetic ops that need to run
// only on non-null output.
template <typename Op>
Expand All @@ -461,6 +516,19 @@ std::shared_ptr<ScalarFunction> MakeUnarySignedArithmeticFunctionNotNull(
return func;
}

const FunctionDoc absolute_value_doc{
"Calculate the absolute value of the argument element-wise",
("Results will wrap around on integer overflow.\n"
"Use function \"abs_checked\" if you want overflow\n"
"to return an error."),
{"x"}};

const FunctionDoc absolute_value_checked_doc{
"Calculate the absolute value of the argument element-wise",
("This function returns an error on overflow. For a variant that\n"
"doesn't fail on overflow, use function \"abs\"."),
{"x"}};

const FunctionDoc add_doc{"Add the arguments element-wise",
("Results will wrap around on integer overflow.\n"
"Use function \"add_checked\" if you want overflow\n"
Expand Down Expand Up @@ -537,6 +605,16 @@ const FunctionDoc pow_checked_doc{
} // namespace

void RegisterScalarArithmetic(FunctionRegistry* registry) {
// ----------------------------------------------------------------------
auto absolute_value =
MakeUnaryArithmeticFunction<AbsoluteValue>("abs", &absolute_value_doc);
DCHECK_OK(registry->AddFunction(std::move(absolute_value)));

// ----------------------------------------------------------------------
auto absolute_value_checked = MakeUnaryArithmeticFunctionNotNull<AbsoluteValueChecked>(
"abs_checked", &absolute_value_checked_doc);
DCHECK_OK(registry->AddFunction(std::move(absolute_value_checked)));

// ----------------------------------------------------------------------
auto add = MakeArithmeticFunction<Add>("add", &add_doc);
DCHECK_OK(registry->AddFunction(std::move(add)));
Expand Down
108 changes: 106 additions & 2 deletions cpp/src/arrow/compute/kernels/scalar_arithmetic_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -933,7 +933,7 @@ TEST(TestBinaryArithmetic, AddWithImplicitCastsUint64EdgeCase) {
}

TEST(TestUnaryArithmetic, DispatchBest) {
for (std::string name : {"negate"}) {
for (std::string name : {"negate", "abs", "abs_checked"}) {
for (const auto& ty : {int8(), int16(), int32(), int64(), uint8(), uint16(), uint32(),
uint64(), float32(), float64()}) {
CheckDispatchBest(name, {ty}, {ty});
Expand All @@ -948,7 +948,7 @@ TEST(TestUnaryArithmetic, DispatchBest) {
}
}

for (std::string name : {"negate", "negate_checked"}) {
for (std::string name : {"negate", "negate_checked", "abs", "abs_checked"}) {
CheckDispatchFails(name, {null()});
}
}
Expand Down Expand Up @@ -1057,5 +1057,109 @@ TYPED_TEST(TestUnaryArithmeticFloating, Negate) {
}
}

TYPED_TEST(TestUnaryArithmeticSigned, AbsoluteValue) {
using CType = typename TestFixture::CType;

auto min = std::numeric_limits<CType>::min();
auto max = std::numeric_limits<CType>::max();

for (auto check_overflow : {false, true}) {
this->SetOverflowCheck(check_overflow);
// Empty array
this->AssertUnaryOp(AbsoluteValue, "[]", "[]");
// Scalar/arrays with nulls
this->AssertUnaryOp(AbsoluteValue, "[null]", "[null]");
this->AssertUnaryOp(AbsoluteValue, "[1, null, -10]", "[1, null, 10]");
this->AssertUnaryOp(AbsoluteValue, this->MakeNullScalar(), this->MakeNullScalar());
// Scalar/arrays with zeros
this->AssertUnaryOp(AbsoluteValue, "[0, -0]", "[0, 0]");
this->AssertUnaryOp(AbsoluteValue, -0, 0);
this->AssertUnaryOp(AbsoluteValue, 0, 0);
// Ordinary scalar/arrays (positive inputs)
this->AssertUnaryOp(AbsoluteValue, "[1, 10, 127]", "[1, 10, 127]");
this->AssertUnaryOp(AbsoluteValue, 1, 1);
this->AssertUnaryOp(AbsoluteValue, this->MakeScalar(1), this->MakeScalar(1));
// Ordinary scalar/arrays (negative inputs)
this->AssertUnaryOp(AbsoluteValue, "[-1, -10, -127]", "[1, 10, 127]");
this->AssertUnaryOp(AbsoluteValue, -1, 1);
this->AssertUnaryOp(AbsoluteValue, MakeArray(-1), "[1]");
// Min/max
this->AssertUnaryOp(AbsoluteValue, max, max);
if (check_overflow) {
this->AssertUnaryOpRaises(AbsoluteValue, MakeArray(min), "overflow");
} else {
this->AssertUnaryOp(AbsoluteValue, min, min);
}
}

// Overflow should not be checked on underlying value slots when output would be null
this->SetOverflowCheck(true);
auto arg = ArrayFromJSON(this->type_singleton(), MakeArray(-1, max, min));
arg = TweakValidityBit(arg, 1, false);
arg = TweakValidityBit(arg, 2, false);
this->AssertUnaryOp(AbsoluteValue, arg, "[1, null, null]");
}

TYPED_TEST(TestUnaryArithmeticUnsigned, AbsoluteValue) {
using CType = typename TestFixture::CType;

auto min = std::numeric_limits<CType>::min();
auto max = std::numeric_limits<CType>::max();

for (auto check_overflow : {false, true}) {
this->SetOverflowCheck(check_overflow);
// Empty arrays
this->AssertUnaryOp(AbsoluteValue, "[]", "[]");
// Array with nulls
this->AssertUnaryOp(AbsoluteValue, "[null]", "[null]");
this->AssertUnaryOp(AbsoluteValue, this->MakeNullScalar(), this->MakeNullScalar());
// Ordinary arrays
this->AssertUnaryOp(AbsoluteValue, "[0, 1, 10, 127]", "[0, 1, 10, 127]");
// Min/max
this->AssertUnaryOp(AbsoluteValue, min, min);
this->AssertUnaryOp(AbsoluteValue, max, max);
}
}

TYPED_TEST(TestUnaryArithmeticFloating, AbsoluteValue) {
using CType = typename TestFixture::CType;

auto min = std::numeric_limits<CType>::lowest();
auto max = std::numeric_limits<CType>::max();

for (auto check_overflow : {false, true}) {
this->SetOverflowCheck(check_overflow);
// Empty array
this->AssertUnaryOp(AbsoluteValue, "[]", "[]");
// Scalar/arrays with nulls
this->AssertUnaryOp(AbsoluteValue, "[null]", "[null]");
this->AssertUnaryOp(AbsoluteValue, "[1.3, null, -10.80]", "[1.3, null, 10.80]");
this->AssertUnaryOp(AbsoluteValue, this->MakeNullScalar(), this->MakeNullScalar());
// Scalars/arrays with zeros
this->AssertUnaryOp(AbsoluteValue, "[0.0, -0.0]", "[0.0, 0.0]");
this->AssertUnaryOp(AbsoluteValue, -0.0F, 0.0F);
this->AssertUnaryOp(AbsoluteValue, 0.0F, 0.0F);
// Ordinary scalars/arrays (positive inputs)
this->AssertUnaryOp(AbsoluteValue, "[1.3, 10.80, 12748.001]",
"[1.3, 10.80, 12748.001]");
this->AssertUnaryOp(AbsoluteValue, 1.3F, 1.3F);
this->AssertUnaryOp(AbsoluteValue, this->MakeScalar(1.3F), this->MakeScalar(1.3F));
// Ordinary scalars/arrays (negative inputs)
this->AssertUnaryOp(AbsoluteValue, "[-1.3, -10.80, -12748.001]",
"[1.3, 10.80, 12748.001]");
this->AssertUnaryOp(AbsoluteValue, -1.3F, 1.3F);
this->AssertUnaryOp(AbsoluteValue, MakeArray(-1.3F), "[1.3]");
// Arrays with infinites
this->AssertUnaryOp(AbsoluteValue, "[Inf, -Inf]", "[Inf, Inf]");
// Arrays with NaNs
this->SetNansEqual(true);
this->AssertUnaryOp(AbsoluteValue, "[NaN]", "[NaN]");
this->AssertUnaryOp(AbsoluteValue, "[-NaN]", "[NaN]");
// Min/max
this->AssertUnaryOp(AbsoluteValue, min, max);
this->AssertUnaryOp(AbsoluteValue, max, max);
}
}

} // namespace compute
} // namespace arrow
2 changes: 1 addition & 1 deletion cpp/src/arrow/util/int_util_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ OPS_WITH_OVERFLOW(DivideWithOverflow, div)
#undef OP_WITH_OVERFLOW
#undef OPS_WITH_OVERFLOW

// Define functions NegateWithOverflow with the signature `bool(T u, T* out)`
// Define function NegateWithOverflow with the signature `bool(T u, T* out)`
// where T is a signed integer type. On overflow, these functions return true.
// Otherwise, false is returned and `out` is updated with the result of the
// operation.
Expand Down
4 changes: 4 additions & 0 deletions docs/source/cpp/compute.rst
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,10 @@ an ``Invalid`` :class:`Status` when overflow is detected.
+--------------------------+------------+--------------------+---------------------+
| Function name | Arity | Input types | Output type |
+==========================+============+====================+=====================+
| abs | Unary | Numeric | Numeric |
+--------------------------+------------+--------------------+---------------------+
| abs_checked | Unary | Numeric | Numeric |
+--------------------------+------------+--------------------+---------------------+
| add | Binary | Numeric | Numeric |
+--------------------------+------------+--------------------+---------------------+
| add_checked | Binary | Numeric | Numeric |
Expand Down
2 changes: 2 additions & 0 deletions docs/source/python/api/compute.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ throws an ``ArrowInvalid`` exception when overflow is detected.
.. autosummary::
:toctree: ../generated/

abs
abs_checked
add
add_checked
divide
Expand Down