From 3a83274d79ba735e18a9fc5b6b2fa7cd6ff3c0c2 Mon Sep 17 00:00:00 2001 From: swayaminsync Date: Sat, 18 Oct 2025 14:27:56 +0530 Subject: [PATCH 1/4] logical_ops impl --- quaddtype/numpy_quaddtype/src/ops.hpp | 86 ++++++++++++ .../src/umath/comparison_ops.cpp | 11 ++ .../numpy_quaddtype/src/umath/unary_ops.cpp | 129 ++++++++++++++++++ quaddtype/release_tracker.md | 12 +- quaddtype/tests/test_quaddtype.py | 79 +++++++++++ 5 files changed, 311 insertions(+), 6 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/ops.hpp b/quaddtype/numpy_quaddtype/src/ops.hpp index 754db054..5f3386ff 100644 --- a/quaddtype/numpy_quaddtype/src/ops.hpp +++ b/quaddtype/numpy_quaddtype/src/ops.hpp @@ -1219,3 +1219,89 @@ ld_greaterequal(const long double *a, const long double *b) { return *a >= *b; } + +// Logical operations + +// Helper function to check if a Sleef_quad value is non-zero (truthy) +static inline npy_bool +quad_is_nonzero(const Sleef_quad *a) +{ + // A value is falsy if it's exactly zero (positive or negative) + // NaN and inf are truthy + // Check both +0.0 and -0.0 since they may not compare equal + Sleef_quad neg_zero = Sleef_negq1_purec(QUAD_ZERO); + + npy_bool is_pos_zero = Sleef_icmpeqq1_purec(*a, QUAD_ZERO); + npy_bool is_neg_zero = Sleef_icmpeqq1_purec(*a, neg_zero); + + return !(is_pos_zero || is_neg_zero); +} + +// Helper function to check if a long double value is non-zero (truthy) +static inline npy_bool +ld_is_nonzero(const long double *a) +{ + // A value is falsy if it's exactly zero (positive or negative) + // NaN and inf are truthy + // In C, 0.0L == -0.0L, so we only need one comparison + return *a != 0.0L; +} + + +static inline npy_bool +quad_logical_and(const Sleef_quad *a, const Sleef_quad *b) +{ + return quad_is_nonzero(a) && quad_is_nonzero(b); +} + +static inline npy_bool +ld_logical_and(const long double *a, const long double *b) +{ + return ld_is_nonzero(a) && ld_is_nonzero(b); +} + + +static inline npy_bool +quad_logical_or(const Sleef_quad *a, const Sleef_quad *b) +{ + return quad_is_nonzero(a) || quad_is_nonzero(b); +} + +static inline npy_bool +ld_logical_or(const long double *a, const long double *b) +{ + return ld_is_nonzero(a) || ld_is_nonzero(b); +} + +static inline npy_bool +quad_logical_xor(const Sleef_quad *a, const Sleef_quad *b) +{ + npy_bool a_truthy = quad_is_nonzero(a); + npy_bool b_truthy = quad_is_nonzero(b); + return (a_truthy && !b_truthy) || (!a_truthy && b_truthy); +} + +static inline npy_bool +ld_logical_xor(const long double *a, const long double *b) +{ + npy_bool a_truthy = ld_is_nonzero(a); + npy_bool b_truthy = ld_is_nonzero(b); + return (a_truthy && !b_truthy) || (!a_truthy && b_truthy); +} + + +// logical not +typedef npy_bool (*unary_logical_quad_def)(const Sleef_quad *); +typedef npy_bool (*unary_logical_longdouble_def)(const long double *); + +static inline npy_bool +quad_logical_not(const Sleef_quad *a) +{ + return !quad_is_nonzero(a); +} + +static inline npy_bool +ld_logical_not(const long double *a) +{ + return !ld_is_nonzero(a); +} \ No newline at end of file diff --git a/quaddtype/numpy_quaddtype/src/umath/comparison_ops.cpp b/quaddtype/numpy_quaddtype/src/umath/comparison_ops.cpp index e4b9579c..8ff2e661 100644 --- a/quaddtype/numpy_quaddtype/src/umath/comparison_ops.cpp +++ b/quaddtype/numpy_quaddtype/src/umath/comparison_ops.cpp @@ -240,5 +240,16 @@ init_quad_comps(PyObject *numpy) return -1; } + // Logical operations (binary: and, or, xor) + if (create_quad_comparison_ufunc(numpy, "logical_and") < 0) { + return -1; + } + if (create_quad_comparison_ufunc(numpy, "logical_or") < 0) { + return -1; + } + if (create_quad_comparison_ufunc(numpy, "logical_xor") < 0) { + return -1; + } + return 0; } \ No newline at end of file diff --git a/quaddtype/numpy_quaddtype/src/umath/unary_ops.cpp b/quaddtype/numpy_quaddtype/src/umath/unary_ops.cpp index 944033fc..ba67d67c 100644 --- a/quaddtype/numpy_quaddtype/src/umath/unary_ops.cpp +++ b/quaddtype/numpy_quaddtype/src/umath/unary_ops.cpp @@ -144,6 +144,129 @@ create_quad_unary_ufunc(PyObject *numpy, const char *ufunc_name) return 0; } +// Logical NOT - returns bool instead of QuadPrecision +static NPY_CASTING +quad_unary_logical_op_resolve_descriptors(PyObject *self, PyArray_DTypeMeta *const dtypes[], + PyArray_Descr *const given_descrs[], PyArray_Descr *loop_descrs[], + npy_intp *NPY_UNUSED(view_offset)) +{ + Py_INCREF(given_descrs[0]); + loop_descrs[0] = given_descrs[0]; + + // Output is always bool + loop_descrs[1] = PyArray_DescrFromType(NPY_BOOL); + if (!loop_descrs[1]) { + return (NPY_CASTING)-1; + } + + return NPY_NO_CASTING; +} + +template +int +quad_logical_not_strided_loop_unaligned(PyArrayMethod_Context *context, char *const data[], + npy_intp const dimensions[], npy_intp const strides[], + NpyAuxData *auxdata) +{ + npy_intp N = dimensions[0]; + char *in_ptr = data[0]; + char *out_ptr = data[1]; + npy_intp in_stride = strides[0]; + npy_intp out_stride = strides[1]; + + QuadPrecDTypeObject *descr = (QuadPrecDTypeObject *)context->descriptors[0]; + QuadBackendType backend = descr->backend; + size_t elem_size = (backend == BACKEND_SLEEF) ? sizeof(Sleef_quad) : sizeof(long double); + + quad_value in; + while (N--) { + memcpy(&in, in_ptr, elem_size); + npy_bool result; + + if (backend == BACKEND_SLEEF) { + result = sleef_op(&in.sleef_value); + } + else { + result = longdouble_op(&in.longdouble_value); + } + + memcpy(out_ptr, &result, sizeof(npy_bool)); + + in_ptr += in_stride; + out_ptr += out_stride; + } + return 0; +} + +template +int +quad_logical_not_strided_loop_aligned(PyArrayMethod_Context *context, char *const data[], + npy_intp const dimensions[], npy_intp const strides[], + NpyAuxData *auxdata) +{ + npy_intp N = dimensions[0]; + char *in_ptr = data[0]; + char *out_ptr = data[1]; + npy_intp in_stride = strides[0]; + npy_intp out_stride = strides[1]; + + QuadPrecDTypeObject *descr = (QuadPrecDTypeObject *)context->descriptors[0]; + QuadBackendType backend = descr->backend; + + while (N--) { + npy_bool result; + + if (backend == BACKEND_SLEEF) { + result = sleef_op((Sleef_quad *)in_ptr); + } + else { + result = longdouble_op((long double *)in_ptr); + } + + *(npy_bool *)out_ptr = result; + + in_ptr += in_stride; + out_ptr += out_stride; + } + return 0; +} + +template +int +create_quad_logical_not_ufunc(PyObject *numpy, const char *ufunc_name) +{ + PyObject *ufunc = PyObject_GetAttrString(numpy, ufunc_name); + if (ufunc == NULL) { + return -1; + } + + PyArray_DTypeMeta *dtypes[2] = {&QuadPrecDType, &PyArray_BoolDType}; + + PyType_Slot slots[] = { + {NPY_METH_resolve_descriptors, (void *)&quad_unary_logical_op_resolve_descriptors}, + {NPY_METH_strided_loop, + (void *)&quad_logical_not_strided_loop_aligned}, + {NPY_METH_unaligned_strided_loop, + (void *)&quad_logical_not_strided_loop_unaligned}, + {0, NULL}}; + + PyArrayMethod_Spec Spec = { + .name = "quad_logical_not", + .nin = 1, + .nout = 1, + .casting = NPY_NO_CASTING, + .flags = NPY_METH_SUPPORTS_UNALIGNED, + .dtypes = dtypes, + .slots = slots, + }; + + if (PyUFunc_AddLoopFromSpec(ufunc, &Spec) < 0) { + return -1; + } + + return 0; +} + int init_quad_unary_ops(PyObject *numpy) { @@ -256,5 +379,11 @@ init_quad_unary_ops(PyObject *numpy) if (create_quad_unary_ufunc(numpy, "deg2rad") < 0) { return -1; } + + // Logical operations (unary: not) + if (create_quad_logical_not_ufunc(numpy, "logical_not") < 0) { + return -1; + } + return 0; } \ No newline at end of file diff --git a/quaddtype/release_tracker.md b/quaddtype/release_tracker.md index b2e2250c..a680b56f 100644 --- a/quaddtype/release_tracker.md +++ b/quaddtype/release_tracker.md @@ -55,8 +55,8 @@ | arccosh | ✅ | ✅ | | arctanh | ✅ | ✅ | | degrees | | | -| radians | ✅ | ✅ | -| deg2rad | ✅ | ✅ | +| radians | ✅ | ✅ | +| deg2rad | ✅ | ✅ | | rad2deg | | | | bitwise_and | | | | bitwise_or | | | @@ -70,10 +70,10 @@ | less_equal | ✅ | ✅ | | not_equal | ✅ | ✅ | | equal | ✅ | ✅ | -| logical_and | | | -| logical_or | | | -| logical_xor | | | -| logical_not | | | +| logical_and | ✅ | ✅ | +| logical_or | ✅ | ✅ | +| logical_xor | ✅ | ✅ | +| logical_not | ✅ | ✅ | | maximum | ✅ | ✅ | | minimum | ✅ | ✅ | | fmax | ✅ | ✅ | diff --git a/quaddtype/tests/test_quaddtype.py b/quaddtype/tests/test_quaddtype.py index 40063cdf..c1fbae93 100644 --- a/quaddtype/tests/test_quaddtype.py +++ b/quaddtype/tests/test_quaddtype.py @@ -150,6 +150,85 @@ def test_array_minmax(op, a, b): quad_res), f"Zero sign mismatch for {op}({a}, {b})" +# Logical operations tests +@pytest.mark.parametrize("op", ["logical_and", "logical_or", "logical_xor"]) +@pytest.mark.parametrize("x1,x2", [ + # Basic cases + (0.0, 0.0), + (0.0, 1.0), + (1.0, 0.0), + (1.0, 2.0), + (2.5, 3.7), + # Negative values + (-1.0, 1.0), + (-2.0, -3.0), + # Negative zero (also falsy) + (-0.0, 0.0), + (-0.0, 1.0), + (1.0, -0.0), + (-0.0, -0.0), + # Special values: NaN and inf are truthy + (np.nan, 0.0), + (0.0, np.nan), + (np.nan, 1.0), + (1.0, np.nan), + (np.nan, np.nan), + (np.inf, 0.0), + (0.0, np.inf), + (np.inf, 1.0), + (np.inf, np.inf), + (-np.inf, 1.0), + (-np.inf, -np.inf), +]) +def test_binary_logical_ops(op, x1, x2): + """Test binary logical operations (and, or, xor) against NumPy's behavior""" + op_func = getattr(np, op) + + # QuadPrecision values + quad_x1 = QuadPrecision(str(x1)) + quad_x2 = QuadPrecision(str(x2)) + quad_result = op_func(quad_x1, quad_x2) + + # NumPy float64 values for comparison + float_x1 = np.float64(x1) + float_x2 = np.float64(x2) + float_result = op_func(float_x1, float_x2) + + # Results should match NumPy's behavior + assert quad_result == float_result, f"{op}({x1}, {x2}): quad={quad_result}, float64={float_result}" + assert isinstance(quad_result, (bool, np.bool_)), f"Result should be bool, got {type(quad_result)}" + + +@pytest.mark.parametrize("x", [ + # Zeros are falsy + 0.0, + -0.0, + # Non-zero values are truthy + 1.0, + -1.0, + 2.5, + -3.7, + 0.001, + # Special values: NaN and inf are truthy + np.nan, + np.inf, + -np.inf, +]) +def test_unary_logical_not(x): + """Test logical_not operation against NumPy's behavior""" + # QuadPrecision value + quad_x = QuadPrecision(str(x)) + quad_result = np.logical_not(quad_x) + + # NumPy float64 value for comparison + float_x = np.float64(x) + float_result = np.logical_not(float_x) + + # Results should match NumPy's behavior + assert quad_result == float_result, f"logical_not({x}): quad={quad_result}, float64={float_result}" + assert isinstance(quad_result, (bool, np.bool_)), f"Result should be bool, got {type(quad_result)}" + + @pytest.mark.parametrize("op", ["amin", "amax", "nanmin", "nanmax"]) @pytest.mark.parametrize("a", ["3.0", "12.5", "100.0", "0.0", "-0.0", "inf", "-inf", "nan", "-nan"]) @pytest.mark.parametrize("b", ["3.0", "12.5", "100.0", "0.0", "-0.0", "inf", "-inf", "nan", "-nan"]) From c8614658b7fedfd67f0743fcb42c2e488ec74141 Mon Sep 17 00:00:00 2001 From: swayaminsync Date: Sat, 18 Oct 2025 14:54:22 +0530 Subject: [PATCH 2/4] isnat is only for datetime dtype --- quaddtype/release_tracker.md | 1 - 1 file changed, 1 deletion(-) diff --git a/quaddtype/release_tracker.md b/quaddtype/release_tracker.md index 905b0886..267fee96 100644 --- a/quaddtype/release_tracker.md +++ b/quaddtype/release_tracker.md @@ -75,7 +75,6 @@ | isfinite | ✅ | ✅ | | isinf | ✅ | ✅ | | isnan | ✅ | ✅ | -| isnat | | | | signbit | ✅ | ✅ | | copysign | ✅ | ✅ | | nextafter | | | From 4fb0032c19c7ed9fc7183b43293ddf5eee15f72c Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Sat, 18 Oct 2025 16:54:40 +0000 Subject: [PATCH 3/4] -0 == 0 --- quaddtype/numpy_quaddtype/src/ops.hpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/ops.hpp b/quaddtype/numpy_quaddtype/src/ops.hpp index 32a8ffc2..2f0133b8 100644 --- a/quaddtype/numpy_quaddtype/src/ops.hpp +++ b/quaddtype/numpy_quaddtype/src/ops.hpp @@ -1247,13 +1247,8 @@ quad_is_nonzero(const Sleef_quad *a) { // A value is falsy if it's exactly zero (positive or negative) // NaN and inf are truthy - // Check both +0.0 and -0.0 since they may not compare equal - Sleef_quad neg_zero = Sleef_negq1_purec(QUAD_ZERO); - - npy_bool is_pos_zero = Sleef_icmpeqq1_purec(*a, QUAD_ZERO); - npy_bool is_neg_zero = Sleef_icmpeqq1_purec(*a, neg_zero); - - return !(is_pos_zero || is_neg_zero); + npy_bool is_zero = Sleef_icmpeqq1_purec(*a, QUAD_ZERO); + return !is_zero; } // Helper function to check if a long double value is non-zero (truthy) @@ -1262,7 +1257,6 @@ ld_is_nonzero(const long double *a) { // A value is falsy if it's exactly zero (positive or negative) // NaN and inf are truthy - // In C, 0.0L == -0.0L, so we only need one comparison return *a != 0.0L; } From a08010b3f4416a25f5e64d1dd24444efc6296980 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Sat, 18 Oct 2025 17:06:28 +0000 Subject: [PATCH 4/4] no specialization --- quaddtype/numpy_quaddtype/src/ops.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quaddtype/numpy_quaddtype/src/ops.hpp b/quaddtype/numpy_quaddtype/src/ops.hpp index 2f0133b8..c76904dd 100644 --- a/quaddtype/numpy_quaddtype/src/ops.hpp +++ b/quaddtype/numpy_quaddtype/src/ops.hpp @@ -1247,7 +1247,7 @@ quad_is_nonzero(const Sleef_quad *a) { // A value is falsy if it's exactly zero (positive or negative) // NaN and inf are truthy - npy_bool is_zero = Sleef_icmpeqq1_purec(*a, QUAD_ZERO); + npy_bool is_zero = Sleef_icmpeqq1(*a, QUAD_ZERO); return !is_zero; }