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
80 changes: 80 additions & 0 deletions quaddtype/numpy_quaddtype/src/ops.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1238,3 +1238,83 @@ 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
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)
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
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);
}
11 changes: 11 additions & 0 deletions quaddtype/numpy_quaddtype/src/umath/comparison_ops.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -240,5 +240,16 @@ init_quad_comps(PyObject *numpy)
return -1;
}

// Logical operations (binary: and, or, xor)
if (create_quad_comparison_ufunc<quad_logical_and, ld_logical_and>(numpy, "logical_and") < 0) {
return -1;
}
if (create_quad_comparison_ufunc<quad_logical_or, ld_logical_or>(numpy, "logical_or") < 0) {
return -1;
}
if (create_quad_comparison_ufunc<quad_logical_xor, ld_logical_xor>(numpy, "logical_xor") < 0) {
return -1;
}

return 0;
}
129 changes: 129 additions & 0 deletions quaddtype/numpy_quaddtype/src/umath/unary_ops.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <unary_logical_quad_def sleef_op, unary_logical_longdouble_def longdouble_op>
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 <unary_logical_quad_def sleef_op, unary_logical_longdouble_def longdouble_op>
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 <unary_logical_quad_def sleef_op, unary_logical_longdouble_def longdouble_op>
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<sleef_op, longdouble_op>},
{NPY_METH_unaligned_strided_loop,
(void *)&quad_logical_not_strided_loop_unaligned<sleef_op, longdouble_op>},
{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)
{
Expand Down Expand Up @@ -262,5 +385,11 @@ init_quad_unary_ops(PyObject *numpy)
if (create_quad_unary_ufunc<quad_radians, ld_radians>(numpy, "deg2rad") < 0) {
return -1;
}

// Logical operations (unary: not)
if (create_quad_logical_not_ufunc<quad_logical_not, ld_logical_not>(numpy, "logical_not") < 0) {
return -1;
}

return 0;
}
9 changes: 4 additions & 5 deletions quaddtype/release_tracker.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,18 +64,17 @@
| less_equal | ✅ | ✅ |
| not_equal | ✅ | ✅ |
| equal | ✅ | ✅ |
| logical_and | | |
| logical_or | | |
| logical_xor | | |
| logical_not | | |
| logical_and | | ✅ |
| logical_or | | ✅ |
| logical_xor | | ✅ |
| logical_not | | ✅ |
| maximum | ✅ | ✅ |
| minimum | ✅ | ✅ |
| fmax | ✅ | ✅ |
| fmin | ✅ | ✅ |
| isfinite | ✅ | ✅ |
| isinf | ✅ | ✅ |
| isnan | ✅ | ✅ |
| isnat | | |
| signbit | ✅ | ✅ |
| copysign | ✅ | ✅ |
| nextafter | | |
Expand Down
79 changes: 79 additions & 0 deletions quaddtype/tests/test_quaddtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"])
Expand Down
Loading