Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
20 changes: 20 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,14 @@ 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, "");
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 +1354,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 +1781,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
50 changes: 49 additions & 1 deletion cpp/src/arrow/compute/kernels/scalar_arithmetic_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1148,6 +1148,10 @@ TEST(TestBinaryArithmetic, DispatchBest) {
CheckDispatchBest("atan2", {float32(), float64()}, {float64(), float64()});
// Integer always promotes to double
CheckDispatchBest("atan2", {float32(), int8()}, {float64(), float64()});
Comment thread
shrivasshankar marked this conversation as resolved.

CheckDispatchBest("hypot", {float32(), float32()}, {float32(), float32()});
CheckDispatchBest("hypot", {float32(), float64()}, {float64(), float64()});
CheckDispatchBest("hypot", {int32(), uint8()}, {float64(), float64()});
}

TEST(TestBinaryArithmetic, Null) {
Expand All @@ -1159,7 +1163,7 @@ TEST(TestBinaryArithmetic, Null) {
}
}

for (std::string name : {"atan2", "bit_wise_and", "bit_wise_or", "bit_wise_xor"}) {
for (std::string name : {"atan2", "bit_wise_and", "bit_wise_or", "bit_wise_xor", "hypot"}) {
Comment thread
rok marked this conversation as resolved.
Outdated
AssertNullToNull(name);
}
}
Expand Down Expand Up @@ -2807,6 +2811,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 docs/source/python/api/compute.rst
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ throws an ``ArrowInvalid`` exception when overflow is detected.
divide_checked
exp
expm1
hypot
multiply
multiply_checked
negate
Expand Down
12 changes: 12 additions & 0 deletions python/pyarrow/tests/test_compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -3905,6 +3905,18 @@ def test_rank_normal_options():
assert result.to_pylist() == expected


@pytest.mark.numpy
def test_hypot():
x = np.array([3.0, 0.0, -5.0, 1.5, 7.25])
y = np.array([4.0, 0.0, 12.0, -2.0, 0.0])
result = pc.hypot(pa.array(x), pa.array(y))
np.testing.assert_array_almost_equal(
result.to_numpy(zero_copy_only=False), np.hypot(x, y))

# scalar inputs
assert pc.hypot(pa.scalar(3.0), pa.scalar(4.0)).as_py() == pytest.approx(5.0)


def create_sample_expressions():
# We need a schema for substrait conversion
schema = pa.schema([pa.field("i64", pa.int64()), pa.field(
Expand Down
Loading