From 6e6a88bb8d7a36b295f8e4858cca7eec1085d9db Mon Sep 17 00:00:00 2001 From: Juniper Tyree <50025784+juntyr@users.noreply.github.com> Date: Sat, 26 Jul 2025 09:12:33 +0000 Subject: [PATCH 1/3] Implement isfinite, isinf, isnan ufuncs --- quaddtype/numpy_quaddtype/src/ops.hpp | 46 ++++++++++++++++++- .../numpy_quaddtype/src/umath/unary_props.cpp | 9 ++++ quaddtype/tests/test_quaddtype.py | 28 +++-------- 3 files changed, 60 insertions(+), 23 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/ops.hpp b/quaddtype/numpy_quaddtype/src/ops.hpp index 50262bf..5988e5f 100644 --- a/quaddtype/numpy_quaddtype/src/ops.hpp +++ b/quaddtype/numpy_quaddtype/src/ops.hpp @@ -1,6 +1,12 @@ #include #include #include +#include + +// Quad Constants, generated with qutil +#define QUAD_ZERO sleef_q(+0x0000000000000LL, 0x0000000000000000ULL, -16383) +#define QUAD_ONE sleef_q(+0x1000000000000LL, 0x0000000000000000ULL, 0) +#define QUAD_POS_INF sleef_q(+0x1000000000000LL, 0x0000000000000000ULL, 16384) // Unary Quad Operations typedef Sleef_quad (*unary_op_quad_def)(const Sleef_quad *); @@ -294,7 +300,27 @@ quad_signbit(const Sleef_quad *op) return Sleef_icmpltq1(one_signed, zero); } -// Unary Quad properties +static inline npy_bool +quad_isfinite(const Sleef_quad *op) +{ + // isfinite(x) = abs(x) < inf + return Sleef_icmpltq1(Sleef_fabsq1(*op), QUAD_POS_INF); +} + +static inline npy_bool +quad_isinf(const Sleef_quad *op) +{ + // isinf(x) = abs(x) == inf + return Sleef_icmpeqq1(Sleef_fabsq1(*op), QUAD_POS_INF); +} + +static inline npy_bool +quad_isnan(const Sleef_quad *op) +{ + return Sleef_iunordq1(*op, *op); +} + +// Unary long double properties typedef npy_bool (*unary_prop_longdouble_def)(const long double *); static inline npy_bool @@ -303,6 +329,24 @@ ld_signbit(const long double *op) return signbit(*op); } +static inline npy_bool +ld_isfinite(const long double *op) +{ + return isfinite(*op); +} + +static inline npy_bool +ld_isinf(const long double *op) +{ + return isinf(*op); +} + +static inline npy_bool +ld_isnan(const long double *op) +{ + return isnan(*op); +} + // Binary Quad operations typedef Sleef_quad (*binary_op_quad_def)(const Sleef_quad *, const Sleef_quad *); diff --git a/quaddtype/numpy_quaddtype/src/umath/unary_props.cpp b/quaddtype/numpy_quaddtype/src/umath/unary_props.cpp index a37983b..7e399ad 100644 --- a/quaddtype/numpy_quaddtype/src/umath/unary_props.cpp +++ b/quaddtype/numpy_quaddtype/src/umath/unary_props.cpp @@ -136,6 +136,15 @@ create_quad_unary_prop_ufunc(PyObject *numpy, const char *ufunc_name) int init_quad_unary_props(PyObject *numpy) { + if (create_quad_unary_prop_ufunc(numpy, "isfinite") < 0) { + return -1; + } + if (create_quad_unary_prop_ufunc(numpy, "isinf") < 0) { + return -1; + } + if (create_quad_unary_prop_ufunc(numpy, "isnan") < 0) { + return -1; + } if (create_quad_unary_prop_ufunc(numpy, "signbit") < 0) { return -1; } diff --git a/quaddtype/tests/test_quaddtype.py b/quaddtype/tests/test_quaddtype.py index af8ca56..d9583e9 100644 --- a/quaddtype/tests/test_quaddtype.py +++ b/quaddtype/tests/test_quaddtype.py @@ -44,13 +44,7 @@ def test_binary_ops(op, other): quad_result = op_func(quad_a, quad_b) float_result = op_func(float_a, float_b) - # FIXME: @juntyr: replace with array_equal once isnan is supported - with np.errstate(invalid="ignore"): - assert ( - (np.float64(quad_result) == float_result) or - (np.abs(np.float64(quad_result) - float_result) < 1e-10) or - ((float_result != float_result) and (quad_result != quad_result)) - ) + np.testing.assert_allclose(np.float64(quad_result), float_result, atol=1e-10, rtol=0, equal_nan=True) @pytest.mark.parametrize("op", ["eq", "ne", "le", "lt", "ge", "gt"]) @@ -95,9 +89,7 @@ def test_array_minmax(op, a, b): quad_res = op_func(quad_a, quad_b) float_res = op_func(float_a, float_b) - # FIXME: @juntyr: replace with array_equal once isnan is supported - with np.errstate(invalid="ignore"): - assert np.all((quad_res == float_res) | ((quad_res != quad_res) & (float_res != float_res))) + np.testing.assert_array_equal(quad_res.astype(float), float_res) @pytest.mark.parametrize("op", ["amin", "amax", "nanmin", "nanmax"]) @@ -114,12 +106,10 @@ def test_array_aminmax(op, a, b): quad_res = op_func(quad_ab) float_res = op_func(float_ab) - # FIXME: @juntyr: replace with array_equal once isnan is supported - with np.errstate(invalid="ignore"): - assert np.all((quad_res == float_res) | ((quad_res != quad_res) & (float_res != float_res))) + np.testing.assert_array_equal(np.array(quad_res).astype(float), float_res) -@pytest.mark.parametrize("op", ["negative", "positive", "absolute", "sign", "signbit"]) +@pytest.mark.parametrize("op", ["negative", "positive", "absolute", "sign", "signbit", "isfinite", "isinf", "isnan"]) @pytest.mark.parametrize("val", ["3.0", "-3.0", "12.5", "100.0", "0.0", "-0.0", "inf", "-inf", "nan", "-nan"]) def test_unary_ops(op, val): op_func = dict(negative=operator.neg, positive=operator.pos, absolute=operator.abs).get(op, None) @@ -135,14 +125,8 @@ def test_unary_ops(op, val): quad_result = of(quad_val) float_result = of(float_val) - # FIXME: @juntyr: replace with array_equal once isnan is supported - with np.errstate(invalid="ignore"): - assert ( - (np.float64(quad_result) == float_result) or - ((float_result != float_result) and (quad_result != quad_result)) - ) and ( - np.signbit(float_result) == np.signbit(quad_result) - ), f"{op}({val}) should be {float_result}, but got {quad_result}" + np.testing.assert_array_equal(np.array(quad_result).astype(float), float_result) + assert np.signbit(float_result) == np.signbit(quad_result) def test_inf(): From 631ce89b0a35832792d08aa30df8aa5685f47e10 Mon Sep 17 00:00:00 2001 From: Juniper Tyree <50025784+juntyr@users.noreply.github.com> Date: Sat, 26 Jul 2025 09:17:03 +0000 Subject: [PATCH 2/3] Update release tracker --- quaddtype/release_tracker.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/quaddtype/release_tracker.md b/quaddtype/release_tracker.md index 83022a7..fa26176 100644 --- a/quaddtype/release_tracker.md +++ b/quaddtype/release_tracker.md @@ -77,9 +77,9 @@ | minimum | ✅ | ✅ | | fmax | | | | fmin | | | -| isfinite | | | -| isinf | | | -| isnan | | | +| isfinite | #121 | ✅ | +| isinf | #121 | ✅ | +| isnan | #221 | ✅ | | isnat | | | | signbit | #122 | ✅ | | copysign | #122 | ✅ | From a7d404c8711debb093619d13a420cd88f380bdf3 Mon Sep 17 00:00:00 2001 From: Juniper Tyree <50025784+juntyr@users.noreply.github.com> Date: Sat, 26 Jul 2025 20:06:13 +0000 Subject: [PATCH 3/3] Use QUAD_ZERO and QUAD_ONE constants --- quaddtype/numpy_quaddtype/src/ops.hpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/ops.hpp b/quaddtype/numpy_quaddtype/src/ops.hpp index 5988e5f..171fc30 100644 --- a/quaddtype/numpy_quaddtype/src/ops.hpp +++ b/quaddtype/numpy_quaddtype/src/ops.hpp @@ -1,7 +1,6 @@ #include #include #include -#include // Quad Constants, generated with qutil #define QUAD_ZERO sleef_q(+0x0000000000000LL, 0x0000000000000000ULL, -16383) @@ -26,8 +25,7 @@ quad_positive(const Sleef_quad *op) static inline Sleef_quad quad_sign(const Sleef_quad *op) { - Sleef_quad zero = Sleef_cast_from_doubleq1(0.0); - int32_t sign = Sleef_icmpq1(*op, zero); + int32_t sign = Sleef_icmpq1(*op, QUAD_ZERO); // sign(x=NaN) = x; otherwise sign(x) in { -1.0; 0.0; +1.0 } return Sleef_iunordq1(*op, *op) ? *op : Sleef_cast_from_int64q1(sign); } @@ -293,11 +291,9 @@ quad_signbit(const Sleef_quad *op) { // FIXME @juntyr or @SwayamInSync: replace with binary implementation // once we test big and little endian in CI - Sleef_quad zero = Sleef_cast_from_doubleq1(0.0); - Sleef_quad one = Sleef_cast_from_doubleq1(1.0); - Sleef_quad one_signed = Sleef_copysignq1(one, *op); + Sleef_quad one_signed = Sleef_copysignq1(QUAD_ONE, *op); // signbit(x) = 1 iff copysign(1, x) == -1 - return Sleef_icmpltq1(one_signed, zero); + return Sleef_icmpltq1(one_signed, QUAD_ZERO); } static inline npy_bool