Skip to content
Open
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 @@ -805,6 +805,7 @@ SCALAR_ARITHMETIC_BINARY(ShiftLeft, "shift_left", "shift_left_checked")
SCALAR_ARITHMETIC_BINARY(ShiftRight, "shift_right", "shift_right_checked")
SCALAR_ARITHMETIC_BINARY(Subtract, "subtract", "subtract_checked")
SCALAR_EAGER_BINARY(Atan2, "atan2")
SCALAR_EAGER_BINARY(Hypot, "hypot")
SCALAR_EAGER_UNARY(Floor, "floor")
SCALAR_EAGER_UNARY(Ceil, "ceil")
SCALAR_EAGER_UNARY(Trunc, "trunc")
Expand Down
9 changes: 9 additions & 0 deletions cpp/src/arrow/compute/api_scalar.h
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,15 @@ Result<Datum> Atan(const Datum& arg, ExecContext* ctx = NULLPTR);
ARROW_EXPORT
Result<Datum> Atan2(const Datum& y, const Datum& x, ExecContext* ctx = NULLPTR);

/// \brief Compute the hypotenuse (Euclidean norm) of x and y, equivalent to
/// sqrt(x^2 + y^2), without undue overflow or underflow at intermediate stages.
/// \param[in] x The x-values to compute the hypotenuse for.
/// \param[in] y The y-values to compute the hypotenuse for.
/// \param[in] ctx the function execution context, optional
/// \return the elementwise hypotenuse of the values
ARROW_EXPORT
Result<Datum> Hypot(const Datum& x, const Datum& y, ExecContext* ctx = NULLPTR);

/// \brief Compute the hyperbolic sine of the array values.
/// \param[in] arg The values to compute the hyperbolic sine for.
/// \param[in] ctx the function execution context, optional
Expand Down
21 changes: 21 additions & 0 deletions cpp/src/arrow/compute/kernels/scalar_arithmetic.cc
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,15 @@ struct Atan2 {
}
};

struct Hypot {
template <typename T, typename Arg0, typename Arg1>
static enable_if_floating_value<Arg0, T> Call(KernelContext*, Arg0 x, Arg1 y, Status*) {
static_assert(std::is_same<T, Arg0>::value, "");
static_assert(std::is_same<Arg0, Arg1>::value, "");
return std::hypot(x, y);
}
};

struct LogNatural {
template <typename T, typename Arg>
static enable_if_floating_value<Arg, T> Call(KernelContext*, Arg arg, Status*) {
Expand Down Expand Up @@ -1346,6 +1355,14 @@ const FunctionDoc atan2_doc{"Compute the inverse tangent of y/x",
("The return value is in the range [-pi, pi]."),
{"y", "x"}};

const FunctionDoc hypot_doc{
"Compute the hypotenuse (Euclidean norm) of x and y",
("The result is equivalent to `sqrt(x^2 + y^2)`, but is computed without\n"
"undue overflow or underflow at intermediate stages of the computation.\n"
"If either x or y is +/-infinity, +infinity is returned, even if the\n"
"other argument is NaN."),
{"x", "y"}};

const FunctionDoc atanh_doc{"Compute the inverse hyperbolic tangent",
("NaN is returned for input values x with \\|x\\| > 1.\n"
"At x = +/- 1, returns +/- infinity.\n"
Expand Down Expand Up @@ -1765,6 +1782,10 @@ void RegisterScalarArithmetic(FunctionRegistry* registry) {
"sqrt_checked", sqrt_checked_doc);
DCHECK_OK(registry->AddFunction(std::move(sqrt_checked)));

// ----------------------------------------------------------------------
auto hypot = MakeArithmeticFunctionFloatingPoint<Hypot>("hypot", hypot_doc);
DCHECK_OK(registry->AddFunction(std::move(hypot)));

// ----------------------------------------------------------------------
auto sign =
MakeUnaryArithmeticFunctionWithFixedIntOutType<Sign, Int8Type>("sign", sign_doc);
Expand Down
44 changes: 44 additions & 0 deletions cpp/src/arrow/compute/kernels/scalar_arithmetic_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2807,6 +2807,50 @@ TYPED_TEST(TestBinaryArithmeticFloating, TrigAtan2) {
-M_PI_2, 0, M_PI));
}

TYPED_TEST(TestBinaryArithmeticFloating, Hypot) {
SKIP_IF_HALF_FLOAT();

this->SetNansEqual(true);
auto hypot = [](const Datum& x, const Datum& y, ArithmeticOptions, ExecContext* ctx) {
return Hypot(x, y, ctx);
};
this->AssertBinop(hypot, "[]", "[]", "[]");
// Pythagorean triples; result is independent of the sign of either argument,
// and hypot(0, 0) == 0.
this->AssertBinop(hypot, "[3, -3, 5, -8, 0]", "[4, -4, -12, 15, 0]",
"[5, 5, 13, 17, 0]");
// Null propagation.
this->AssertBinop(hypot, "[1, null, 0]", "[null, 1, 0]", "[null, null, 0]");
// NaN propagates, unless the other argument is infinite (per C99/IEEE 754,
// hypot(+/-Inf, NaN) == +Inf).
this->AssertBinop(hypot, "[NaN, 1, NaN, Inf]", "[1, NaN, NaN, NaN]",
"[NaN, NaN, NaN, Inf]");
// +/-infinity in either argument yields +infinity.
this->AssertBinop(hypot, "[Inf, -Inf, 3]", "[4, 0, -Inf]", "[Inf, Inf, Inf]");
}

// hypot avoids overflow/underflow at intermediate stages: for float32 the
// squares below overflow to +Inf, so a naive sqrt(x*x + y*y) would return Inf,
// while the kernel (like std::hypot) returns the correct finite result.
TEST(TestBinaryArithmetic, HypotOverflowSafety) {
std::vector<float> xs = {3.0e30f, 5.0e37f, -2.0e30f};
std::vector<float> ys = {4.0e30f, 1.2e38f, 0.0f};
ASSERT_TRUE(std::isinf(xs[0] * xs[0])); // the naive intermediate overflows

std::vector<float> expected_vals;
for (size_t i = 0; i < xs.size(); ++i) {
expected_vals.push_back(std::hypot(xs[i], ys[i]));
}

std::shared_ptr<Array> x, y, expected;
ArrayFromVector<FloatType>(xs, &x);
ArrayFromVector<FloatType>(ys, &y);
ArrayFromVector<FloatType>(expected_vals, &expected);

ASSERT_OK_AND_ASSIGN(Datum result, Hypot(x, y));
AssertArraysEqual(*expected, *result.make_array(), /*verbose=*/true);
}

TYPED_TEST(TestUnaryArithmeticFloating, TrigAtanh) {
SKIP_IF_HALF_FLOAT();

Expand Down
6 changes: 6 additions & 0 deletions docs/source/cpp/compute.rst
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,8 @@ Mixed time resolution temporal inputs will be cast to finest input resolution.
+------------------+--------+-------------------------+-------------------------------+-------+
| expm1 | Unary | Numeric | Float32/Float64 | |
+------------------+--------+-------------------------+-------------------------------+-------+
| hypot | Binary | Numeric | Float32/Float64 | \(3) |
+------------------+--------+-------------------------+-------------------------------+-------+
| multiply | Binary | Numeric/Temporal | Numeric/Temporal | \(1) |
+------------------+--------+-------------------------+-------------------------------+-------+
| multiply_checked | Binary | Numeric/Temporal | Numeric/Temporal | \(1) |
Expand Down Expand Up @@ -560,6 +562,10 @@ Mixed time resolution temporal inputs will be cast to finest input resolution.
values return NaN. Integral and decimal values return signedness as Int8 and
floating-point values return it with the same type as the input values.

* \(3) Computes ``sqrt(x^2 + y^2)`` without undue overflow or underflow at
intermediate stages of the computation. If either argument is infinite, the
result is ``+Inf`` even if the other argument is NaN.

Bit-wise functions
~~~~~~~~~~~~~~~~~~

Expand Down
1 change: 1 addition & 0 deletions python/pyarrow/tests/test_compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -3932,6 +3932,7 @@ def create_sample_expressions():
pc.multiply(a, b), pc.power(a, a), pc.sqrt(a),
pc.exp(b), pc.cos(b), pc.sin(b), pc.tan(b),
pc.acos(b), pc.atan(b), pc.asin(b), pc.atan2(b, b),
pc.hypot(b, b),
pc.sinh(a), pc.cosh(a), pc.tanh(a),
pc.asinh(a), pc.acosh(b), pc.atanh(k),
pc.abs(b), pc.sign(a), pc.bit_wise_not(a),
Expand Down
Loading