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 numberDiff line numberDiff line change
Expand Up@@ -787,6 +787,7 @@ Result<Datum> RoundToMultiple(const Datum& arg, RoundToMultipleOptions options,
SCALAR_ARITHMETIC_BINARY(Add, "add", "add_checked")
SCALAR_ARITHMETIC_BINARY(Divide, "divide", "divide_checked")
SCALAR_ARITHMETIC_BINARY(Logb, "logb", "logb_checked")
SCALAR_ARITHMETIC_BINARY(Modulo, "modulo", "modulo_checked")
SCALAR_ARITHMETIC_BINARY(Multiply, "multiply", "multiply_checked")
SCALAR_ARITHMETIC_BINARY(Power, "power", "power_checked")
SCALAR_ARITHMETIC_BINARY(ShiftLeft, "shift_left", "shift_left_checked")
Expand Down
15 changes: 15 additions & 0 deletions cpp/src/arrow/compute/api_scalar.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -632,6 +632,21 @@ Result<Datum> Subtract(const Datum& left, const Datum& right,
ArithmeticOptions options = ArithmeticOptions(),
ExecContext* ctx = NULLPTR);

/// \brief Get the modulo of dividing two values.
/// Array values must be the same length.
/// If either argument is null the result will be null.
/// For integer types, if there is a zero divisor, an error will be raised.
///
/// \param[in] left the dividend
/// \param[in] right the divisor
/// \param[in] options arithmetic options (enable/disable overflow checking), optional
/// \param[in] ctx the function execution context, optional
/// \return the elementwise remainder
ARROW_EXPORT
Result<Datum> Modulo(const Datum& left, const Datum& right,
ArithmeticOptions options = ArithmeticOptions(),
ExecContext* ctx = NULLPTR);

/// \brief Multiply two values. Array values must be the same length. If either
/// factor is null the result will be null.
///
Expand Down
57 changes: 57 additions & 0 deletions cpp/src/arrow/compute/kernels/base_arithmetic_internal.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,6 +31,7 @@ namespace arrow {

using internal::AddWithOverflow;
using internal::DivideWithOverflow;
using internal::ModuloWithOverflow;
using internal::MultiplyWithOverflow;
using internal::NegateWithOverflow;
using internal::SubtractWithOverflow;
Expand DownExpand Up@@ -464,6 +465,62 @@ struct FloatingDivideChecked {
// TODO: Add decimal
};

struct Modulo {
template <typename T, typename Arg0, typename Arg1>
static enable_if_floating_value<T> Call(KernelContext*, Arg0 left, Arg1 right,
Status* st) {
*st = Status::Invalid("Not implemented");
return 0;
}

template <typename T, typename Arg0, typename Arg1>
static enable_if_integer_value<T> Call(KernelContext*, Arg0 left, Arg1 right,
Status* st) {
if (ARROW_PREDICT_FALSE(right == 0)) {
*st = Status::Invalid("Modulo by zero");
return 0;
}

return left % right;
}

template <typename T, typename Arg0, typename Arg1>
static enable_if_decimal_value<T> Call(KernelContext* ctx, Arg0 left, Arg1 right,
Status* st) {
return Divide::Call<T>(ctx, left, right, st);
}
};

struct ModuloChecked {
template <typename T, typename Arg0, typename Arg1>
static enable_if_floating_value<T> Call(KernelContext*, Arg0 left, Arg1 right,
Status* st) {
*st = Status::Invalid("Not implemented");
return 0;
}

template <typename T, typename Arg0, typename Arg1>
static enable_if_integer_value<T> Call(KernelContext*, Arg0 left, Arg1 right,
Status* st) {
static_assert(std::is_same<T, Arg0>::value && std::is_same<T, Arg1>::value, "");
T result;
if (ARROW_PREDICT_FALSE(ModuloWithOverflow(left, right, &result))) {
if (right == 0) {
*st = Status::Invalid("Modulo by zero");
} else {
*st = Status::Invalid("Overflow");
}
}
return result;
}

template <typename T, typename Arg0, typename Arg1>
static enable_if_decimal_value<T> Call(KernelContext* ctx, Arg0 left, Arg1 right,
Status* st) {
return Divide::Call<T>(ctx, left, right, st);
}
};

struct Negate {
template <typename T, typename Arg>
static constexpr enable_if_floating_value<T> Call(KernelContext*, Arg arg, Status*) {
Expand Down
26 changes: 24 additions & 2 deletions cpp/src/arrow/compute/kernels/scalar_arithmetic.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -673,7 +673,7 @@ void AddDecimalBinaryKernels(const std::string& name, ScalarFunction* func) {
out_type = OutputType(ResolveDecimalAdditionOrSubtractionOutput);
} else if (op == "multiply") {
out_type = OutputType(ResolveDecimalMultiplicationOutput);
} else if (op == "divide") {
} else if (op == "divide" || op == "modulo") {
out_type = OutputType(ResolveDecimalDivisionOutput);
} else {
DCHECK(false);
Expand DownExpand Up@@ -764,7 +764,7 @@ struct ArithmeticFunction : ScalarFunction {
return CastBinaryDecimalArgs(DecimalPromotion::kAdd, types);
} else if (op == "multiply") {
return CastBinaryDecimalArgs(DecimalPromotion::kMultiply, types);
} else if (op == "divide") {
} else if (op == "divide" || op == "modulo") {
return CastBinaryDecimalArgs(DecimalPromotion::kDivide, types);
} else {
return Status::Invalid("Invalid decimal function: ", func_name);
Expand DownExpand Up@@ -1149,6 +1149,18 @@ const FunctionDoc div_checked_doc{
"integer overflow is encountered."),
{"dividend", "divisor"}};

const FunctionDoc modulo_doc{
"Get the modulo of dividing two values",
("Integer division by zero returns an error.\n"
"Use function \"modulo_checked\" if you want to get an error\n"
"in all the aforementioned cases."),
{"dividend", "divisor"}};

const FunctionDoc modulo_checked_doc{
"Get the modulo of dividing two values",
("An error is returned when trying to divide by zero."),
{"dividend", "divisor"}};

const FunctionDoc negate_doc{"Negate the argument element-wise",
("Results will wrap around on integer overflow.\n"
"Use function \"negate_checked\" if you want overflow\n"
Expand DownExpand Up@@ -1692,6 +1704,16 @@ void RegisterScalarArithmetic(FunctionRegistry* registry) {

DCHECK_OK(registry->AddFunction(std::move(divide_checked)));

// ----------------------------------------------------------------------
auto modulo = MakeArithmeticFunctionNotNull<Modulo>("modulo", modulo_doc);
AddDecimalBinaryKernels<Modulo>("modulo", modulo.get());
DCHECK_OK(registry->AddFunction(std::move(modulo)));

auto modulo_checked =
MakeArithmeticFunctionNotNull<ModuloChecked>("modulo_checked", modulo_checked_doc);
AddDecimalBinaryKernels<ModuloChecked>("modulo_checked", modulo_checked.get());
DCHECK_OK(registry->AddFunction(std::move(modulo_checked)));

// ----------------------------------------------------------------------
auto negate = MakeUnaryArithmeticFunction<Negate>("negate", negate_doc);
AddDecimalUnaryKernels<Negate>(negate.get());
Expand Down
41 changes: 41 additions & 0 deletions cpp/src/arrow/compute/kernels/scalar_arithmetic_test.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -900,6 +900,47 @@ TYPED_TEST(TestBinaryArithmeticSigned, DivideOverflowRaises) {
this->AssertBinop(Divide, MakeArray(min), MakeArray(-1), "[0]");
}

TYPED_TEST(TestBinaryArithmeticIntegral, Modulo) {
for (auto check_overflow : {false, true}) {
this->SetOverflowCheck(check_overflow);

// Empty arrays
this->AssertBinop(Modulo, "[]", "[]", "[]");
// Ordinary arrays
this->AssertBinop(Modulo, "[3, 2, 6]", "[1, 1, 2]", "[0, 0, 0]");
// Array with nulls
this->AssertBinop(Modulo, "[null, 10, 30, null, 20]", "[1, 4, 2, 5, 10]",
"[null, 2, 0, null, 0]");
// Scalar % Array
this->AssertBinop(Modulo, 33, "[null, 1, 3, null, 2]", "[null, 0, 0, null, 1]");
// Array % Scalar
this->AssertBinop(Modulo, "[null, 10, 30, null, 2]", 3, "[null, 1, 0, null, 2]");
// Scalar % Scalar
this->AssertBinop(Modulo, 16, 7, 2);
}
}

TYPED_TEST(TestBinaryArithmeticSigned, Modulo) {
// Ordinary arrays
this->AssertBinop(Modulo, "[-3, 2, -7]", "[1, 1, 2]", "[0, 0, -1]");
// Array with nulls
this->AssertBinop(Modulo, "[null, 10, 30, null, -21]", "[1, 4, 2, 5, 10]",
"[null, 2, 0, null, -1]");
// Scalar % Array
this->AssertBinop(Modulo, 33, "[null, -1, -3, null, 2]", "[null, 0, 0, null, 1]");
// Array % Scalar
this->AssertBinop(Modulo, "[null, 10, 30, null, 2]", 3, "[null, 1, 0, null, 2]");
// Scalar % Scalar
this->AssertBinop(Modulo, -17, -8, -1);
}

TYPED_TEST(TestBinaryArithmeticIntegral, ModuloByZero) {
for (auto check_overflow : {false, true}) {
this->SetOverflowCheck(check_overflow);
this->AssertBinopRaises(Modulo, "[3, 2, 6]", "[1, 1, 0]", "Modulo by zero");
}
}

TYPED_TEST(TestBinaryArithmeticFloating, Power) {
using CType = typename TestFixture::CType;
auto max = std::numeric_limits<CType>::max();
Expand Down
8 changes: 8 additions & 0 deletions cpp/src/arrow/util/basic_decimal.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -1398,6 +1398,14 @@ BasicDecimal256 operator/(const BasicDecimal256& left, const BasicDecimal256& ri
return result;
}

BasicDecimal256 operator%(const BasicDecimal256& left, const BasicDecimal256& right) {
BasicDecimal256 remainder;
BasicDecimal256 result;
auto s = left.Divide(right, &result, &remainder);
DCHECK_EQ(s, DecimalStatus::kSuccess);
return remainder;
}

// Explicitly instantiate template base class, for DLL linking on Windows
template class GenericBasicDecimal<BasicDecimal128, 128>;
template class GenericBasicDecimal<BasicDecimal256, 256>;
Expand Down
2 changes: 2 additions & 0 deletions cpp/src/arrow/util/basic_decimal.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -883,5 +883,7 @@ ARROW_EXPORT BasicDecimal256 operator*(const BasicDecimal256& left,
const BasicDecimal256& right);
ARROW_EXPORT BasicDecimal256 operator/(const BasicDecimal256& left,
const BasicDecimal256& right);
ARROW_EXPORT BasicDecimal256 operator%(const BasicDecimal256& left,
const BasicDecimal256& right);

} // namespace arrow
1 change: 1 addition & 0 deletions cpp/src/arrow/util/int_util_overflow.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,6 +59,7 @@ OPS_WITH_OVERFLOW(AddWithOverflow, add)
OPS_WITH_OVERFLOW(SubtractWithOverflow, sub)
OPS_WITH_OVERFLOW(MultiplyWithOverflow, mul)
OPS_WITH_OVERFLOW(DivideWithOverflow, div)
OPS_WITH_OVERFLOW(ModuloWithOverflow, mod)

#undef OP_WITH_OVERFLOW
#undef OPS_WITH_OVERFLOW
Expand Down