8000 Avoid spurious double cast in _monotonicity by LemonBoy · Pull Request #24449 · numpy/numpy · GitHub
[go: up one dir, main page]

Skip to content

Avoid spurious double cast in _monotonicity #24449

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Avoid spurious double cast in _monotonicity
Rewrite the underlying C implementation of the monotonicity check in a
generic fashion, allowing the use on arrays with comparable types.

Closes #11022
  • Loading branch information
LemonBoy authored and Giuseppe Musacchio committed Sep 7, 2023
commit aeb66202a0426023cf47a4cb6723d917e824a354
75 changes: 40 additions & 35 deletions numpy/core/src/multiarray/compiled_base.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,46 +30,47 @@ typedef enum {
* and 0 if the array is not monotonic.
*/
static int
check_array_monotonic(const double *a, npy_intp lena)
check_array_monotonic(const char *data, npy_intp size, npy_intp stride, PyArrayObject *arr)
{
npy_intp i;
double next;
double last;
npy_intp count = size;
PyArray_CompareFunc *compare = PyArray_DESCR(arr)->f->compare;
const char *elem_ptr = data;

if (lena == 0) {
if (count == 0) {
/* all bin edges hold the same value */
return 1;
}
last = a[0];

/* Skip repeated values at the beginning of the array */
for (i = 1; (i < lena) && (a[i] == last); i++);
do {
elem_ptr += stride;
count--;
} while (count > 0 && compare(elem_ptr, data, arr) == 0);

if (i == lena) {
if (count == 0) {
/* all bin edges hold the same value */
return 1;
}

next = a[i];
if (last < next) {
if (compare(elem_ptr, data, arr) > 0) {
/* Possibly monotonic increasing */
for (i += 1; i < lena; i++) {
last = next;
next = a[i];
if (last > next) {
while (count--) {
const char *prev_elem_ptr = elem_ptr - stride;
if (compare(prev_elem_ptr, elem_ptr, arr) > 0) {
return 0;
}
elem_ptr += stride;
}
return 1;
}
else {
/* last > next, possibly monotonic decreasing */
for (i += 1; i < lena; i++) {
last = next;
next = a[i];
if (last < next) {
while (count--) {
const char *prev_elem_ptr = elem_ptr - stride;
if (compare(prev_elem_ptr, elem_ptr, arr) < 0) {
return 0;
}
elem_ptr += stride;
}
return -1;
}
Expand Down Expand Up @@ -233,34 +234,38 @@ NPY_NO_EXPORT PyObject *
arr__monotonicity(PyObject *NPY_UNUSED(self), PyObject *args, PyObject *kwds)
{
static char *kwlist[] = {"x", NULL};
PyObject *obj_x = NULL;
PyArrayObject *arr_x = NULL;
PyObject *array0 = NULL;
PyArrayObject *array = NULL;
long monotonic;
npy_intp len_x;
NPY_BEGIN_THREADS_DEF;

if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:_monotonicity", kwlist,
&obj_x)) {
&array0)) {
return NULL;
}

/*
* TODO:
* `x` could be strided, needs change to check_array_monotonic
* `x` is forced to double for this check
*/
arr_x = (PyArrayObject *)PyArray_FROMANY(
obj_x, NPY_DOUBLE, 1, 1, NPY_ARRAY_CARRAY_RO);
if (arr_x == NULL) {
array = (PyArrayObject *)PyArray_FromArray((PyArrayObject *)array0, NULL,
NPY_ARRAY_CARRAY_RO | NPY_ARRAY_NOTSWAPPED);
if (array == NULL) {
return NULL;
}

len_x = PyArray_SIZE(arr_x);
NPY_BEGIN_THREADS_THRESHOLDED(len_x)
monotonic = check_array_monotonic(
(const double *)PyArray_DATA(arr_x), len_x);
if (PyArray_DESCR(array)->f->compare == NULL) {
PyErr_SetString(PyExc_TypeError,
"type does not have compare function");
Py_DECREF(array);
return -1;
}

assert(PyArray_TRIVIALLY_ITERABLE(array));

NPY_BEGIN_THREADS_DESCR(PyArray_DESCR(array));
monotonic = check_array_monotonic((const char *)PyArray_DATA(array),
PyArray_SIZE(array),
PyArray_STRIDE(array, 0),
array);
NPY_END_THREADS
Py_DECREF(arr_x);
Py_DECREF(array);

return PyLong_FromLong(monotonic);
}
Expand Down
2 changes: 2 additions & 0 deletions numpy/lib/_function_base_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -5653,6 +5653,8 @@ def digitize(x, bins, right=False):
# here for compatibility, searchsorted below is happy to take this
if np.issubdtype(x.dtype, _nx.complexfloating):
raise TypeError("x may not be complex")
if np.issubdtype(bins.dtype, _nx.complexfloating):
raise TypeError("bins may not be complex")

mono = _monotonicity(bins)
if mono == 0:
Expand Down
2 changes: 0 additions & 2 deletions numpy/lib/tests/test_function_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1967,8 +1967,6 @@ def test_large_integers_increasing(self):
x = 2**54 # loses precision in a float
assert_equal(np.digitize(x, [x - 1, x + 1]), 1)

@pytest.mark.xfail(
reason="gh-11022: np.core.multiarray._monoticity loses precision")
def test_large_integers_decreasing(self):
# gh-11022
x = 2**54 # loses precision in a float
Expand Down
0