Skip to content

Commit 2f4847a

Browse files
authored
Merge branch 'numpy:main' into macos-intel
2 parents 28a19e7 + ed7458a commit 2f4847a

File tree

5 files changed

+303
-5
lines changed

5 files changed

+303
-5
lines changed

quaddtype/numpy_quaddtype/src/ops.hpp

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1238,3 +1238,83 @@ ld_greaterequal(const long double *a, const long double *b)
12381238
{
12391239
return *a >= *b;
12401240
}
1241+
1242+
// Logical operations
1243+
1244+
// Helper function to check if a Sleef_quad value is non-zero (truthy)
1245+
static inline npy_bool
1246+
quad_is_nonzero(const Sleef_quad *a)
1247+
{
1248+
// A value is falsy if it's exactly zero (positive or negative)
1249+
// NaN and inf are truthy
1250+
npy_bool is_zero = Sleef_icmpeqq1(*a, QUAD_ZERO);
1251+
return !is_zero;
1252+
}
1253+
1254+
// Helper function to check if a long double value is non-zero (truthy)
1255+
static inline npy_bool
1256+
ld_is_nonzero(const long double *a)
1257+
{
1258+
// A value is falsy if it's exactly zero (positive or negative)
1259+
// NaN and inf are truthy
1260+
return *a != 0.0L;
1261+
}
1262+
1263+
1264+
static inline npy_bool
1265+
quad_logical_and(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_and(const long double *a, const long double *b)
1272+
{
1273+
return ld_is_nonzero(a) && ld_is_nonzero(b);
1274+
}
1275+
1276+
1277+
static inline npy_bool
1278+
quad_logical_or(const Sleef_quad *a, const Sleef_quad *b)
1279+
{
1280+
return quad_is_nonzero(a) || quad_is_nonzero(b);
1281+
}
1282+
1283+
static inline npy_bool
1284+
ld_logical_or(const long double *a, const long double *b)
1285+
{
1286+
return ld_is_nonzero(a) || ld_is_nonzero(b);
1287+
}
1288+
1289+
static inline npy_bool
1290+
quad_logical_xor(const Sleef_quad *a, const Sleef_quad *b)
1291+
{
1292+
npy_bool a_truthy = quad_is_nonzero(a);
1293+
npy_bool b_truthy = quad_is_nonzero(b);
1294+
return (a_truthy && !b_truthy) || (!a_truthy && b_truthy);
1295+
}
1296+
1297+
static inline npy_bool
1298+
ld_logical_xor(const long double *a, const long double *b)
1299+
{
1300+
npy_bool a_truthy = ld_is_nonzero(a);
1301+
npy_bool b_truthy = ld_is_nonzero(b);
1302+
return (a_truthy && !b_truthy) || (!a_truthy && b_truthy);
1303+
}
1304+
1305+
1306+
// logical not
1307+
typedef npy_bool (*unary_logical_quad_def)(const Sleef_quad *);
1308+
typedef npy_bool (*unary_logical_longdouble_def)(const long double *);
1309+
1310+
static inline npy_bool
1311+
quad_logical_not(const Sleef_quad *a)
1312+
{
1313+
return !quad_is_nonzero(a);
1314+
}
1315+
1316+
static inline npy_bool
1317+
ld_logical_not(const long double *a)
1318+
{
1319+
return !ld_is_nonzero(a);
1320+
}

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
{
@@ -262,5 +385,11 @@ init_quad_unary_ops(PyObject *numpy)
262385
if (create_quad_unary_ufunc<quad_radians, ld_radians>(numpy, "deg2rad") < 0) {
263386
return -1;
264387
}
388+
389+
// Logical operations (unary: not)
390+
if (create_quad_logical_not_ufunc<quad_logical_not, ld_logical_not>(numpy, "logical_not") < 0) {
391+
return -1;
392+
}
393+
265394
return 0;
266395
}

quaddtype/release_tracker.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,18 +64,17 @@
6464
| less_equal |||
6565
| not_equal |||
6666
| equal |||
67-
| logical_and | | |
68-
| logical_or | | |
69-
| logical_xor | | |
70-
| logical_not | | |
67+
| logical_and | | |
68+
| logical_or | | |
69+
| logical_xor | | |
70+
| logical_not | | |
7171
| maximum |||
7272
| minimum |||
7373
| fmax |||
7474
| fmin |||
7575
| isfinite |||
7676
| isinf |||
7777
| isnan |||
78-
| isnat | | |
7978
| signbit |||
8079
| copysign |||
8180
| nextafter | | |

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)