diff --git a/quaddtype/numpy_quaddtype/src/ops.hpp b/quaddtype/numpy_quaddtype/src/ops.hpp index 2c37ec5a..826cab57 100644 --- a/quaddtype/numpy_quaddtype/src/ops.hpp +++ b/quaddtype/numpy_quaddtype/src/ops.hpp @@ -838,6 +838,13 @@ quad_heaviside(const Sleef_quad *x1, const Sleef_quad *x2) } } +static inline Sleef_quad +quad_hypot(const Sleef_quad *x1, const Sleef_quad *x2) +{ + // hypot(x1, x2) = sqrt(x1^2 + x2^2) + return Sleef_hypotq1_u05(*x1, *x2); +} + // Binary long double operations typedef long double (*binary_op_longdouble_def)(const long double *, const long double *); // Binary long double operations with 2 outputs (for divmod, modf, frexp) @@ -1108,6 +1115,14 @@ ld_heaviside(const long double *x1, const long double *x2) } } +static inline long double +ld_hypot(const long double *x1, const long double *x2) +{ + // hypot(x1, x2) = sqrt(x1^2 + x2^2) + // Use the standard library hypotl function + return hypotl(*x1, *x2); +} + // comparison quad functions typedef npy_bool (*cmp_quad_def)(const Sleef_quad *, const Sleef_quad *); diff --git a/quaddtype/numpy_quaddtype/src/umath/binary_ops.cpp b/quaddtype/numpy_quaddtype/src/umath/binary_ops.cpp index b1417747..9cc19a91 100644 --- a/quaddtype/numpy_quaddtype/src/umath/binary_ops.cpp +++ b/quaddtype/numpy_quaddtype/src/umath/binary_ops.cpp @@ -457,6 +457,9 @@ init_quad_binary_ops(PyObject *numpy) if (create_quad_binary_ufunc(numpy, "heaviside") < 0) { return -1; } + if (create_quad_binary_ufunc(numpy, "hypot") < 0) { + return -1; + } if (create_quad_binary_2out_ufunc(numpy, "divmod") < 0) { return -1; } diff --git a/quaddtype/release_tracker.md b/quaddtype/release_tracker.md index 048aca19..e9b84f37 100644 --- a/quaddtype/release_tracker.md +++ b/quaddtype/release_tracker.md @@ -29,9 +29,6 @@ | heaviside | ✅ | ✅ | | conj | ✅ | ✅ | | conjugate | ✅ | ✅ | -| heaviside | ✅ | ✅ | -| conj | | | -| conjugate | | | | exp | ✅ | ✅ | | exp2 | ✅ | ✅ | | log | ✅ | ✅ | @@ -50,7 +47,7 @@ | arccos | ✅ | ❌ _Need: basic tests + edge cases (NaN/inf/±1/out-of-domain)_ | | arctan | ✅ | ❌ _Need: basic tests + edge cases (NaN/inf/0/asymptotes)_ | | arctan2 | ✅ | ❌ _Need: basic tests + edge cases (NaN/inf/0/quadrant coverage)_ | -| hypot | | | +| hypot | ✅ | ✅ | | sinh | ✅ | ✅ | | cosh | ✅ | ✅ | | tanh | ✅ | ✅ | diff --git a/quaddtype/tests/test_quaddtype.py b/quaddtype/tests/test_quaddtype.py index b0c807ff..1a0bc97d 100644 --- a/quaddtype/tests/test_quaddtype.py +++ b/quaddtype/tests/test_quaddtype.py @@ -1859,3 +1859,49 @@ def test_conj_conjugate_identity(func, value): else: assert result == x + +@pytest.mark.parametrize("x1,x2,expected", [ + # Basic Pythagorean triples + (3.0, 4.0, 5.0), + (5.0, 12.0, 13.0), + # Zero cases + (0.0, 0.0, 0.0), + (0.0, 5.0, 5.0), + (5.0, 0.0, 5.0), + # Negative values (hypot uses absolute values) + (-3.0, -4.0, 5.0), + (-3.0, 4.0, 5.0), + (3.0, -4.0, 5.0), + # Symmetry + (3.14159265358979323846, 2.71828182845904523536, None), # Will test symmetry + (2.71828182845904523536, 3.14159265358979323846, None), # Will test symmetry + # Infinity cases + (np.inf, 0.0, np.inf), + (0.0, np.inf, np.inf), + (np.inf, np.inf, np.inf), + (-np.inf, 0.0, np.inf), + (np.inf, -np.inf, np.inf), + # NaN cases + (np.nan, 3.0, np.nan), + (3.0, np.nan, np.nan), + (np.nan, np.nan, np.nan), +]) +def test_hypot(x1, x2, expected): + """Test hypot ufunc with various edge cases""" + q1 = QuadPrecision(x1) + q2 = QuadPrecision(x2) + result = np.hypot(q1, q2) + + assert isinstance(result, QuadPrecision) + + if expected is None: + # Symmetry test - just check the values are equal + result_reverse = np.hypot(q2, q1) + assert result == result_reverse + elif np.isnan(expected): + assert np.isnan(float(result)) + elif np.isinf(expected): + assert np.isinf(float(result)) + else: + np.testing.assert_allclose(float(result), expected, rtol=1e-13) +