Skip to content

Commit 3a83274

Browse files
committed
logical_ops impl
1 parent 120f01f commit 3a83274

File tree

5 files changed

+311
-6
lines changed

5 files changed

+311
-6
lines changed

quaddtype/numpy_quaddtype/src/ops.hpp

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1219,3 +1219,89 @@ ld_greaterequal(const long double *a, const long double *b)
12191219
{
12201220
return *a >= *b;
12211221
}
1222+
1223+
// Logical operations
1224+
1225+
// Helper function to check if a Sleef_quad value is non-zero (truthy)
1226+
static inline npy_bool
1227+
quad_is_nonzero(const Sleef_quad *a)
1228+
{
1229+
// A value is falsy if it's exactly zero (positive or negative)
1230+
// NaN and inf are truthy
1231+
// Check both +0.0 and -0.0 since they may not compare equal
1232+
Sleef_quad neg_zero = Sleef_negq1_purec(QUAD_ZERO);
1233+
1234+
npy_bool is_pos_zero = Sleef_icmpeqq1_purec(*a, QUAD_ZERO);
1235+
npy_bool is_neg_zero = Sleef_icmpeqq1_purec(*a, neg_zero);
1236+
1237+
return !(is_pos_zero || is_neg_zero);
1238+
}
1239+
1240+
// Helper function to check if a long double value is non-zero (truthy)
1241+
static inline npy_bool
1242+
ld_is_nonzero(const long double *a)
1243+
{
1244+
// A value is falsy if it's exactly zero (positive or negative)
1245+
// NaN and inf are truthy
1246+
// In C, 0.0L == -0.0L, so we only need one comparison
1247+
return *a != 0.0L;
1248+
}
1249+
1250+
1251+
static inline npy_bool
1252+
quad_logical_and(const Sleef_quad *a, const Sleef_quad *b)
1253+
{
1254+
return quad_is_nonzero(a) && quad_is_nonzero(b);
1255+
}
1256+
1257+
static inline npy_bool
1258+
ld_logical_and(const long double *a, const long double *b)
1259+
{
1260+
return ld_is_nonzero(a) && ld_is_nonzero(b);
1261+
}
1262+
1263+
1264+
static inline npy_bool
1265+
quad_logical_or(const Sleef_quad *a, const Sleef_quad *b)
1266+
{
1267+
return quad_is_nonzero(a) || quad_is_nonzero(b);
1268+
}
1269+
1270+
static inline npy_bool
1271+
ld_logical_or(const long double *a, const long double *b)
1272+
{
1273+
return ld_is_nonzero(a) || ld_is_nonzero(b);
1274+
}
1275+
1276+
static inline npy_bool
1277+
quad_logical_xor(const Sleef_quad *a, const Sleef_quad *b)
1278+
{
1279+
npy_bool a_truthy = quad_is_nonzero(a);
1280+
npy_bool b_truthy = quad_is_nonzero(b);
1281+
return (a_truthy && !b_truthy) || (!a_truthy && b_truthy);
1282+
}
1283+
1284+
static inline npy_bool
1285+
ld_logical_xor(const long double *a, const long double *b)
1286+
{
1287+
npy_bool a_truthy = ld_is_nonzero(a);
1288+
npy_bool b_truthy = ld_is_nonzero(b);
1289+
return (a_truthy && !b_truthy) || (!a_truthy && b_truthy);
1290+
}
1291+
1292+
1293+
// logical not
1294+
typedef npy_bool (*unary_logical_quad_def)(const Sleef_quad *);
1295+
typedef npy_bool (*unary_logical_longdouble_def)(const long double *);
1296+
1297+
static inline npy_bool
1298+
quad_logical_not(const Sleef_quad *a)
1299+
{
1300+
return !quad_is_nonzero(a);
1301+
}
1302+
1303+
static inline npy_bool
1304+
ld_logical_not(const long double *a)
1305+
{
1306+
return !ld_is_nonzero(a);
1307+
}

quaddtype/numpy_quaddtype/src/umath/comparison_ops.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,5 +240,16 @@ init_quad_comps(PyObject *numpy)
240240
return -1;
241241
}
242242

243+
// Logical operations (binary: and, or, xor)
244+
if (create_quad_comparison_ufunc<quad_logical_and, ld_logical_and>(numpy, "logical_and") < 0) {
245+
return -1;
246+
}
247+
if (create_quad_comparison_ufunc<quad_logical_or, ld_logical_or>(numpy, "logical_or") < 0) {
248+
return -1;
249+
}
250+
if (create_quad_comparison_ufunc<quad_logical_xor, ld_logical_xor>(numpy, "logical_xor") < 0) {
251+
return -1;
252+
}
253+
243254
return 0;
244255
}

quaddtype/numpy_quaddtype/src/umath/unary_ops.cpp

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,129 @@ create_quad_unary_ufunc(PyObject *numpy, const char *ufunc_name)
144144
return 0;
145145
}
146146

147+
// Logical NOT - returns bool instead of QuadPrecision
148+
static NPY_CASTING
149+
quad_unary_logical_op_resolve_descriptors(PyObject *self, PyArray_DTypeMeta *const dtypes[],
150+
PyArray_Descr *const given_descrs[], PyArray_Descr *loop_descrs[],
151+
npy_intp *NPY_UNUSED(view_offset))
152+
{
153+
Py_INCREF(given_descrs[0]);
154+
loop_descrs[0] = given_descrs[0];
155+
156+
// Output is always bool
157+
loop_descrs[1] = PyArray_DescrFromType(NPY_BOOL);
158+
if (!loop_descrs[1]) {
159+
return (NPY_CASTING)-1;
160+
}
161+
162+
return NPY_NO_CASTING;
163+
}
164+
165+
template <unary_logical_quad_def sleef_op, unary_logical_longdouble_def longdouble_op>
166+
int
167+
quad_logical_not_strided_loop_unaligned(PyArrayMethod_Context *context, char *const data[],
168+
npy_intp const dimensions[], npy_intp const strides[],
169+
NpyAuxData *auxdata)
170+
{
171+
npy_intp N = dimensions[0];
172+
char *in_ptr = data[0];
173+
char *out_ptr = data[1];
174+
npy_intp in_stride = strides[0];
175+
npy_intp out_stride = strides[1];
176+
177+
QuadPrecDTypeObject *descr = (QuadPrecDTypeObject *)context->descriptors[0];
178+
QuadBackendType backend = descr->backend;
179+
size_t elem_size = (backend == BACKEND_SLEEF) ? sizeof(Sleef_quad) : sizeof(long double);
180+
181+
quad_value in;
182+
while (N--) {
183+
memcpy(&in, in_ptr, elem_size);
184+
npy_bool result;
185+
186+
if (backend == BACKEND_SLEEF) {
187+
result = sleef_op(&in.sleef_value);
188+
}
189+
else {
190+
result = longdouble_op(&in.longdouble_value);
191+
}
192+
193+
memcpy(out_ptr, &result, sizeof(npy_bool));
194+
195+
in_ptr += in_stride;
196+
out_ptr += out_stride;
197+
}
198+
return 0;
199+
}
200+
201+
template <unary_logical_quad_def sleef_op, unary_logical_longdouble_def longdouble_op>
202+
int
203+
quad_logical_not_strided_loop_aligned(PyArrayMethod_Context *context, char *const data[],
204+
npy_intp const dimensions[], npy_intp const strides[],
205+
NpyAuxData *auxdata)
206+
{
207+
npy_intp N = dimensions[0];
208+
char *in_ptr = data[0];
209+
char *out_ptr = data[1];
210+
npy_intp in_stride = strides[0];
211+
npy_intp out_stride = strides[1];
212+
213+
QuadPrecDTypeObject *descr = (QuadPrecDTypeObject *)context->descriptors[0];
214+
QuadBackendType backend = descr->backend;
215+
216+
while (N--) {
217+
npy_bool result;
218+
219+
if (backend == BACKEND_SLEEF) {
220+
result = sleef_op((Sleef_quad *)in_ptr);
221+
}
222+
else {
223+
result = longdouble_op((long double *)in_ptr);
224+
}
225+
226+
*(npy_bool *)out_ptr = result;
227+
228+
in_ptr += in_stride;
229+
out_ptr += out_stride;
230+
}
231+
return 0;
232+
}
233+
234+
template <unary_logical_quad_def sleef_op, unary_logical_longdouble_def longdouble_op>
235+
int
236+
create_quad_logical_not_ufunc(PyObject *numpy, const char *ufunc_name)
237+
{
238+
PyObject *ufunc = PyObject_GetAttrString(numpy, ufunc_name);
239+
if (ufunc == NULL) {
240+
return -1;
241+
}
242+
243+
PyArray_DTypeMeta *dtypes[2] = {&QuadPrecDType, &PyArray_BoolDType};
244+
245+
PyType_Slot slots[] = {
246+
{NPY_METH_resolve_descriptors, (void *)&quad_unary_logical_op_resolve_descriptors},
247+
{NPY_METH_strided_loop,
248+
(void *)&quad_logical_not_strided_loop_aligned<sleef_op, longdouble_op>},
249+
{NPY_METH_unaligned_strided_loop,
250+
(void *)&quad_logical_not_strided_loop_unaligned<sleef_op, longdouble_op>},
251+
{0, NULL}};
252+
253+
PyArrayMethod_Spec Spec = {
254+
.name = "quad_logical_not",
255+
.nin = 1,
256+
.nout = 1,
257+
.casting = NPY_NO_CASTING,
258+
.flags = NPY_METH_SUPPORTS_UNALIGNED,
259+
.dtypes = dtypes,
260+
.slots = slots,
261+
};
262+
263+
if (PyUFunc_AddLoopFromSpec(ufunc, &Spec) < 0) {
264+
return -1;
265+
}
266+
267+
return 0;
268+
}
269+
147270
int
148271
init_quad_unary_ops(PyObject *numpy)
149272
{
@@ -256,5 +379,11 @@ init_quad_unary_ops(PyObject *numpy)
256379
if (create_quad_unary_ufunc<quad_radians, ld_radians>(numpy, "deg2rad") < 0) {
257380
return -1;
258381
}
382+
383+
// Logical operations (unary: not)
384+
if (create_quad_logical_not_ufunc<quad_logical_not, ld_logical_not>(numpy, "logical_not") < 0) {
385+
return -1;
386+
}
387+
259388
return 0;
260389
}

quaddtype/release_tracker.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@
5555
| arccosh |||
5656
| arctanh |||
5757
| degrees | | |
58-
| radians |||
59-
| deg2rad |||
58+
| radians || |
59+
| deg2rad || |
6060
| rad2deg | | |
6161
| bitwise_and | | |
6262
| bitwise_or | | |
@@ -70,10 +70,10 @@
7070
| less_equal |||
7171
| not_equal |||
7272
| equal |||
73-
| logical_and | | |
74-
| logical_or | | |
75-
| logical_xor | | |
76-
| logical_not | | |
73+
| logical_and | | |
74+
| logical_or | | |
75+
| logical_xor | | |
76+
| logical_not | | |
7777
| maximum |||
7878
| minimum |||
7979
| fmax |||

quaddtype/tests/test_quaddtype.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,85 @@ def test_array_minmax(op, a, b):
150150
quad_res), f"Zero sign mismatch for {op}({a}, {b})"
151151

152152

153+
# Logical operations tests
154+
@pytest.mark.parametrize("op", ["logical_and", "logical_or", "logical_xor"])
155+
@pytest.mark.parametrize("x1,x2", [
156+
# Basic cases
157+
(0.0, 0.0),
158+
(0.0, 1.0),
159+
(1.0, 0.0),
160+
(1.0, 2.0),
161+
(2.5, 3.7),
162+
# Negative values
163+
(-1.0, 1.0),
164+
(-2.0, -3.0),
165+
# Negative zero (also falsy)
166+
(-0.0, 0.0),
167+
(-0.0, 1.0),
168+
(1.0, -0.0),
169+
(-0.0, -0.0),
170+
# Special values: NaN and inf are truthy
171+
(np.nan, 0.0),
172+
(0.0, np.nan),
173+
(np.nan, 1.0),
174+
(1.0, np.nan),
175+
(np.nan, np.nan),
176+
(np.inf, 0.0),
177+
(0.0, np.inf),
178+
(np.inf, 1.0),
179+
(np.inf, np.inf),
180+
(-np.inf, 1.0),
181+
(-np.inf, -np.inf),
182+
])
183+
def test_binary_logical_ops(op, x1, x2):
184+
"""Test binary logical operations (and, or, xor) against NumPy's behavior"""
185+
op_func = getattr(np, op)
186+
187+
# QuadPrecision values
188+
quad_x1 = QuadPrecision(str(x1))
189+
quad_x2 = QuadPrecision(str(x2))
190+
quad_result = op_func(quad_x1, quad_x2)
191+
192+
# NumPy float64 values for comparison
193+
float_x1 = np.float64(x1)
194+
float_x2 = np.float64(x2)
195+
float_result = op_func(float_x1, float_x2)
196+
197+
# Results should match NumPy's behavior
198+
assert quad_result == float_result, f"{op}({x1}, {x2}): quad={quad_result}, float64={float_result}"
199+
assert isinstance(quad_result, (bool, np.bool_)), f"Result should be bool, got {type(quad_result)}"
200+
201+
202+
@pytest.mark.parametrize("x", [
203+
# Zeros are falsy
204+
0.0,
205+
-0.0,
206+
# Non-zero values are truthy
207+
1.0,
208+
-1.0,
209+
2.5,
210+
-3.7,
211+
0.001,
212+
# Special values: NaN and inf are truthy
213+
np.nan,
214+
np.inf,
215+
-np.inf,
216+
])
217+
def test_unary_logical_not(x):
218+
"""Test logical_not operation against NumPy's behavior"""
219+
# QuadPrecision value
220+
quad_x = QuadPrecision(str(x))
221+
quad_result = np.logical_not(quad_x)
222+
223+
# NumPy float64 value for comparison
224+
float_x = np.float64(x)
225+
float_result = np.logical_not(float_x)
226+
227+
# Results should match NumPy's behavior
228+
assert quad_result == float_result, f"logical_not({x}): quad={quad_result}, float64={float_result}"
229+
assert isinstance(quad_result, (bool, np.bool_)), f"Result should be bool, got {type(quad_result)}"
230+
231+
153232
@pytest.mark.parametrize("op", ["amin", "amax", "nanmin", "nanmax"])
154233
@pytest.mark.parametrize("a", ["3.0", "12.5", "100.0", "0.0", "-0.0", "inf", "-inf", "nan", "-nan"])
155234
@pytest.mark.parametrize("b", ["3.0", "12.5", "100.0", "0.0", "-0.0", "inf", "-inf", "nan", "-nan"])

0 commit comments

Comments
 (0)