Skip to content

Commit 1978af2

Browse files
[C++][Python] Add "hypot" compute kernel
Add a binary floating-point compute function "hypot" that computes the hypotenuse sqrt(x^2 + y^2) without undue overflow or underflow at intermediate stages, mirroring numpy.hypot and std::hypot. The kernel reuses the existing MakeArithmeticFunctionFloatingPoint factory (as atan2 does), registering float32 and float64 kernels and promoting integer/decimal inputs to float64 via DispatchBest. Also adds the Hypot() convenience function and declaration, a FunctionDoc, C++ tests (including an overflow-safety test that exercises inputs whose squares overflow float32), a compute.rst entry, and pyarrow expression coverage.
1 parent 55958e0 commit 1978af2

6 files changed

Lines changed: 82 additions & 0 deletions

File tree

cpp/src/arrow/compute/api_scalar.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,7 @@ SCALAR_ARITHMETIC_BINARY(ShiftLeft, "shift_left", "shift_left_checked")
805805
SCALAR_ARITHMETIC_BINARY(ShiftRight, "shift_right", "shift_right_checked")
806806
SCALAR_ARITHMETIC_BINARY(Subtract, "subtract", "subtract_checked")
807807
SCALAR_EAGER_BINARY(Atan2, "atan2")
808+
SCALAR_EAGER_BINARY(Hypot, "hypot")
808809
SCALAR_EAGER_UNARY(Floor, "floor")
809810
SCALAR_EAGER_UNARY(Ceil, "ceil")
810811
SCALAR_EAGER_UNARY(Trunc, "trunc")

cpp/src/arrow/compute/api_scalar.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -806,6 +806,15 @@ Result<Datum> Atan(const Datum& arg, ExecContext* ctx = NULLPTR);
806806
ARROW_EXPORT
807807
Result<Datum> Atan2(const Datum& y, const Datum& x, ExecContext* ctx = NULLPTR);
808808

809+
/// \brief Compute the hypotenuse (Euclidean norm) of x and y, equivalent to
810+
/// sqrt(x^2 + y^2), without undue overflow or underflow at intermediate stages.
811+
/// \param[in] x The x-values to compute the hypotenuse for.
812+
/// \param[in] y The y-values to compute the hypotenuse for.
813+
/// \param[in] ctx the function execution context, optional
814+
/// \return the elementwise hypotenuse of the values
815+
ARROW_EXPORT
816+
Result<Datum> Hypot(const Datum& x, const Datum& y, ExecContext* ctx = NULLPTR);
817+
809818
/// \brief Compute the hyperbolic sine of the array values.
810819
/// \param[in] arg The values to compute the hyperbolic sine for.
811820
/// \param[in] ctx the function execution context, optional

cpp/src/arrow/compute/kernels/scalar_arithmetic.cc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,15 @@ struct Atan2 {
368368
}
369369
};
370370

371+
struct Hypot {
372+
template <typename T, typename Arg0, typename Arg1>
373+
static enable_if_floating_value<Arg0, T> Call(KernelContext*, Arg0 x, Arg1 y, Status*) {
374+
static_assert(std::is_same<T, Arg0>::value, "");
375+
static_assert(std::is_same<Arg0, Arg1>::value, "");
376+
return std::hypot(x, y);
377+
}
378+
};
379+
371380
struct LogNatural {
372381
template <typename T, typename Arg>
373382
static enable_if_floating_value<Arg, T> Call(KernelContext*, Arg arg, Status*) {
@@ -1346,6 +1355,14 @@ const FunctionDoc atan2_doc{"Compute the inverse tangent of y/x",
13461355
("The return value is in the range [-pi, pi]."),
13471356
{"y", "x"}};
13481357

1358+
const FunctionDoc hypot_doc{
1359+
"Compute the hypotenuse (Euclidean norm) of x and y",
1360+
("The result is equivalent to `sqrt(x^2 + y^2)`, but is computed without\n"
1361+
"undue overflow or underflow at intermediate stages of the computation.\n"
1362+
"If either x or y is +/-infinity, +infinity is returned, even if the\n"
1363+
"other argument is NaN."),
1364+
{"x", "y"}};
1365+
13491366
const FunctionDoc atanh_doc{"Compute the inverse hyperbolic tangent",
13501367
("NaN is returned for input values x with \\|x\\| > 1.\n"
13511368
"At x = +/- 1, returns +/- infinity.\n"
@@ -1765,6 +1782,10 @@ void RegisterScalarArithmetic(FunctionRegistry* registry) {
17651782
"sqrt_checked", sqrt_checked_doc);
17661783
DCHECK_OK(registry->AddFunction(std::move(sqrt_checked)));
17671784

1785+
// ----------------------------------------------------------------------
1786+
auto hypot = MakeArithmeticFunctionFloatingPoint<Hypot>("hypot", hypot_doc);
1787+
DCHECK_OK(registry->AddFunction(std::move(hypot)));
1788+
17681789
// ----------------------------------------------------------------------
17691790
auto sign =
17701791
MakeUnaryArithmeticFunctionWithFixedIntOutType<Sign, Int8Type>("sign", sign_doc);

cpp/src/arrow/compute/kernels/scalar_arithmetic_test.cc

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2807,6 +2807,50 @@ TYPED_TEST(TestBinaryArithmeticFloating, TrigAtan2) {
28072807
-M_PI_2, 0, M_PI));
28082808
}
28092809

2810+
TYPED_TEST(TestBinaryArithmeticFloating, Hypot) {
2811+
SKIP_IF_HALF_FLOAT();
2812+
2813+
this->SetNansEqual(true);
2814+
auto hypot = [](const Datum& x, const Datum& y, ArithmeticOptions, ExecContext* ctx) {
2815+
return Hypot(x, y, ctx);
2816+
};
2817+
this->AssertBinop(hypot, "[]", "[]", "[]");
2818+
// Pythagorean triples; result is independent of the sign of either argument,
2819+
// and hypot(0, 0) == 0.
2820+
this->AssertBinop(hypot, "[3, -3, 5, -8, 0]", "[4, -4, -12, 15, 0]",
2821+
"[5, 5, 13, 17, 0]");
2822+
// Null propagation.
2823+
this->AssertBinop(hypot, "[1, null, 0]", "[null, 1, 0]", "[null, null, 0]");
2824+
// NaN propagates, unless the other argument is infinite (per C99/IEEE 754,
2825+
// hypot(+/-Inf, NaN) == +Inf).
2826+
this->AssertBinop(hypot, "[NaN, 1, NaN, Inf]", "[1, NaN, NaN, NaN]",
2827+
"[NaN, NaN, NaN, Inf]");
2828+
// +/-infinity in either argument yields +infinity.
2829+
this->AssertBinop(hypot, "[Inf, -Inf, 3]", "[4, 0, -Inf]", "[Inf, Inf, Inf]");
2830+
}
2831+
2832+
// hypot avoids overflow/underflow at intermediate stages: for float32 the
2833+
// squares below overflow to +Inf, so a naive sqrt(x*x + y*y) would return Inf,
2834+
// while the kernel (like std::hypot) returns the correct finite result.
2835+
TEST(TestBinaryArithmetic, HypotOverflowSafety) {
2836+
std::vector<float> xs = {3.0e30f, 5.0e37f, -2.0e30f};
2837+
std::vector<float> ys = {4.0e30f, 1.2e38f, 0.0f};
2838+
ASSERT_TRUE(std::isinf(xs[0] * xs[0])); // the naive intermediate overflows
2839+
2840+
std::vector<float> expected_vals;
2841+
for (size_t i = 0; i < xs.size(); ++i) {
2842+
expected_vals.push_back(std::hypot(xs[i], ys[i]));
2843+
}
2844+
2845+
std::shared_ptr<Array> x, y, expected;
2846+
ArrayFromVector<FloatType>(xs, &x);
2847+
ArrayFromVector<FloatType>(ys, &y);
2848+
ArrayFromVector<FloatType>(expected_vals, &expected);
2849+
2850+
ASSERT_OK_AND_ASSIGN(Datum result, Hypot(x, y));
2851+
AssertArraysEqual(*expected, *result.make_array(), /*verbose=*/true);
2852+
}
2853+
28102854
TYPED_TEST(TestUnaryArithmeticFloating, TrigAtanh) {
28112855
SKIP_IF_HALF_FLOAT();
28122856

docs/source/cpp/compute.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,8 @@ Mixed time resolution temporal inputs will be cast to finest input resolution.
512512
+------------------+--------+-------------------------+-------------------------------+-------+
513513
| expm1 | Unary | Numeric | Float32/Float64 | |
514514
+------------------+--------+-------------------------+-------------------------------+-------+
515+
| hypot | Binary | Numeric | Float32/Float64 | \(3) |
516+
+------------------+--------+-------------------------+-------------------------------+-------+
515517
| multiply | Binary | Numeric/Temporal | Numeric/Temporal | \(1) |
516518
+------------------+--------+-------------------------+-------------------------------+-------+
517519
| multiply_checked | Binary | Numeric/Temporal | Numeric/Temporal | \(1) |
@@ -560,6 +562,10 @@ Mixed time resolution temporal inputs will be cast to finest input resolution.
560562
values return NaN. Integral and decimal values return signedness as Int8 and
561563
floating-point values return it with the same type as the input values.
562564

565+
* \(3) Computes ``sqrt(x^2 + y^2)`` without undue overflow or underflow at
566+
intermediate stages of the computation. If either argument is infinite, the
567+
result is ``+Inf`` even if the other argument is NaN.
568+
563569
Bit-wise functions
564570
~~~~~~~~~~~~~~~~~~
565571

python/pyarrow/tests/test_compute.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3932,6 +3932,7 @@ def create_sample_expressions():
39323932
pc.multiply(a, b), pc.power(a, a), pc.sqrt(a),
39333933
pc.exp(b), pc.cos(b), pc.sin(b), pc.tan(b),
39343934
pc.acos(b), pc.atan(b), pc.asin(b), pc.atan2(b, b),
3935+
pc.hypot(b, b),
39353936
pc.sinh(a), pc.cosh(a), pc.tanh(a),
39363937
pc.asinh(a), pc.acosh(b), pc.atanh(k),
39373938
pc.abs(b), pc.sign(a), pc.bit_wise_not(a),

0 commit comments

Comments
 (0)