summaryrefslogtreecommitdiff
path: root/numpy
diff options
context:
space:
mode:
authorMatti Picus <matti.picus@gmail.com>2022-06-14 08:04:04 +0300
committerGitHub <noreply@github.com>2022-06-14 08:04:04 +0300
commitabc26a351e14c890bdd67785ed4f3c60c637b0fa (patch)
tree7a18ddfc91041ed2a5225a75370358983df967aa /numpy
parent2dcb99cb7c4541837882c90c82aae49ab9d8e684 (diff)
parenta546ee19523f9b97db985afa8d161af0dee1fafa (diff)
downloadnumpy-abc26a351e14c890bdd67785ed4f3c60c637b0fa.tar.gz
Merge pull request #21437 from seberg/fpe-in-cast
ENH: Add floating point error checking to (almost) all casts
Diffstat (limited to 'numpy')
-rw-r--r--numpy/core/include/numpy/ndarraytypes.h5
-rw-r--r--numpy/core/src/common/lowlevel_strided_loops.h16
-rw-r--r--numpy/core/src/common/umathmodule.h6
-rw-r--r--numpy/core/src/multiarray/array_assign_array.c49
-rw-r--r--numpy/core/src/multiarray/array_assign_scalar.c49
-rw-r--r--numpy/core/src/multiarray/array_coercion.c29
-rw-r--r--numpy/core/src/multiarray/array_method.h20
-rw-r--r--numpy/core/src/multiarray/arraytypes.c.src80
-rw-r--r--numpy/core/src/multiarray/convert_datatype.c26
-rw-r--r--numpy/core/src/multiarray/ctors.c27
-rw-r--r--numpy/core/src/multiarray/dtype_transfer.c115
-rw-r--r--numpy/core/src/multiarray/lowlevel_strided_loops.c.src63
-rw-r--r--numpy/core/src/multiarray/mapping.c174
-rw-r--r--numpy/core/src/multiarray/mapping.h2
-rw-r--r--numpy/core/src/multiarray/nditer_api.c22
-rw-r--r--numpy/core/src/multiarray/nditer_constr.c24
-rw-r--r--numpy/core/src/multiarray/nditer_impl.h41
-rw-r--r--numpy/core/src/umath/extobj.c27
-rw-r--r--numpy/core/src/umath/ufunc_object.c23
-rw-r--r--numpy/core/tests/test_array_coercion.py25
-rw-r--r--numpy/core/tests/test_casting_floatingpoint_errors.py153
-rw-r--r--numpy/core/tests/test_half.py10
-rw-r--r--numpy/core/tests/test_indexing.py5
-rw-r--r--numpy/core/tests/test_numeric.py4
-rw-r--r--numpy/core/tests/test_regression.py18
-rw-r--r--numpy/core/tests/test_ufunc.py47
-rw-r--r--numpy/ma/tests/test_core.py11
27 files changed, 832 insertions, 239 deletions
diff --git a/numpy/core/include/numpy/ndarraytypes.h b/numpy/core/include/numpy/ndarraytypes.h
index c295f34bb..97e0f4e2a 100644
--- a/numpy/core/include/numpy/ndarraytypes.h
+++ b/numpy/core/include/numpy/ndarraytypes.h
@@ -1380,7 +1380,10 @@ typedef struct {
int nd_fancy;
npy_intp fancy_dims[NPY_MAXDIMS];
- /* Whether the iterator (any of the iterators) requires API */
+ /*
+ * Whether the iterator (any of the iterators) requires API. This is
+ * unused by NumPy itself; ArrayMethod flags are more precise.
+ */
int needs_api;
/*
diff --git a/numpy/core/src/common/lowlevel_strided_loops.h b/numpy/core/src/common/lowlevel_strided_loops.h
index 118ce9cb1..924a34db5 100644
--- a/numpy/core/src/common/lowlevel_strided_loops.h
+++ b/numpy/core/src/common/lowlevel_strided_loops.h
@@ -196,7 +196,7 @@ PyArray_GetDTypeTransferFunction(int aligned,
PyArray_Descr *src_dtype, PyArray_Descr *dst_dtype,
int move_references,
NPY_cast_info *cast_info,
- int *out_needs_api);
+ NPY_ARRAYMETHOD_FLAGS *out_flags);
NPY_NO_EXPORT int
get_fields_transfer_function(int aligned,
@@ -205,7 +205,7 @@ get_fields_transfer_function(int aligned,
int move_references,
PyArrayMethod_StridedLoop **out_stransfer,
NpyAuxData **out_transferdata,
- int *out_needs_api);
+ NPY_ARRAYMETHOD_FLAGS *out_flags);
NPY_NO_EXPORT int
get_subarray_transfer_function(int aligned,
@@ -214,7 +214,7 @@ get_subarray_transfer_function(int aligned,
int move_references,
PyArrayMethod_StridedLoop **out_stransfer,
NpyAuxData **out_transferdata,
- int *out_needs_api);
+ NPY_ARRAYMETHOD_FLAGS *out_flags);
/*
* This is identical to PyArray_GetDTypeTransferFunction, but returns a
@@ -241,7 +241,7 @@ PyArray_GetMaskedDTypeTransferFunction(int aligned,
PyArray_Descr *mask_dtype,
int move_references,
NPY_cast_info *cast_info,
- int *out_needs_api);
+ NPY_ARRAYMETHOD_FLAGS *out_flags);
/*
* Casts the specified number of elements from 'src' with data type
@@ -336,10 +336,14 @@ mapiter_trivial_set(PyArrayObject *self, PyArrayObject *ind,
PyArrayObject *result);
NPY_NO_EXPORT int
-mapiter_get(PyArrayMapIterObject *mit);
+mapiter_get(
+ PyArrayMapIterObject *mit, NPY_cast_info *cast_info,
+ NPY_ARRAYMETHOD_FLAGS flags, int is_aligned);
NPY_NO_EXPORT int
-mapiter_set(PyArrayMapIterObject *mit);
+mapiter_set(
+ PyArrayMapIterObject *mit, NPY_cast_info *cast_info,
+ NPY_ARRAYMETHOD_FLAGS flags, int is_aligned);
/*
* Prepares shape and strides for a simple raw array iteration.
diff --git a/numpy/core/src/common/umathmodule.h b/numpy/core/src/common/umathmodule.h
index fe44fe403..0c69f8f54 100644
--- a/numpy/core/src/common/umathmodule.h
+++ b/numpy/core/src/common/umathmodule.h
@@ -7,8 +7,14 @@
NPY_NO_EXPORT PyObject *
get_sfloat_dtype(PyObject *NPY_UNUSED(mod), PyObject *NPY_UNUSED(args));
+/* Defined in umath/extobj.c */
+NPY_NO_EXPORT int
+PyUFunc_GiveFloatingpointErrors(const char *name, int fpe_errors);
+
PyObject * add_newdoc_ufunc(PyObject *NPY_UNUSED(dummy), PyObject *args);
PyObject * ufunc_frompyfunc(PyObject *NPY_UNUSED(dummy), PyObject *args, PyObject *NPY_UNUSED(kwds));
+
+
int initumath(PyObject *m);
#endif /* NUMPY_CORE_SRC_COMMON_UMATHMODULE_H_ */
diff --git a/numpy/core/src/multiarray/array_assign_array.c b/numpy/core/src/multiarray/array_assign_array.c
index 020a7f29a..9d5bf6875 100644
--- a/numpy/core/src/multiarray/array_assign_array.c
+++ b/numpy/core/src/multiarray/array_assign_array.c
@@ -8,11 +8,13 @@
*/
#define NPY_NO_DEPRECATED_API NPY_API_VERSION
#define _MULTIARRAYMODULE
+#define _UMATHMODULE
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "numpy/ndarraytypes.h"
+#include "numpy/npy_math.h"
#include "npy_config.h"
#include "npy_pycompat.h"
@@ -25,6 +27,8 @@
#include "array_assign.h"
#include "dtype_transfer.h"
+#include "umathmodule.h"
+
/*
* Check that array data is both uint-aligned and true-aligned for all array
* elements, as required by the copy/casting code in lowlevel_strided_loops.c
@@ -83,7 +87,7 @@ raw_array_assign_array(int ndim, npy_intp const *shape,
npy_intp src_strides_it[NPY_MAXDIMS];
npy_intp coord[NPY_MAXDIMS];
- int aligned, needs_api = 0;
+ int aligned;
NPY_BEGIN_THREADS_DEF;
@@ -116,15 +120,19 @@ raw_array_assign_array(int ndim, npy_intp const *shape,
/* Get the function to do the casting */
NPY_cast_info cast_info;
+ NPY_ARRAYMETHOD_FLAGS flags;
if (PyArray_GetDTypeTransferFunction(aligned,
src_strides_it[0], dst_strides_it[0],
src_dtype, dst_dtype,
0,
- &cast_info, &needs_api) != NPY_SUCCEED) {
+ &cast_info, &flags) != NPY_SUCCEED) {
return -1;
}
- if (!needs_api) {
+ if (!(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
+ npy_clear_floatstatus_barrier(src_data);
+ }
+ if (!(flags & NPY_METH_REQUIRES_PYAPI)) {
NPY_BEGIN_THREADS;
}
@@ -143,6 +151,14 @@ raw_array_assign_array(int ndim, npy_intp const *shape,
NPY_END_THREADS;
NPY_cast_info_xfree(&cast_info);
+
+ if (!(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
+ int fpes = npy_get_floatstatus_barrier(src_data);
+ if (fpes && PyUFunc_GiveFloatingpointErrors("cast", fpes) < 0) {
+ return -1;
+ }
+ }
+
return 0;
fail:
NPY_END_THREADS;
@@ -170,7 +186,7 @@ raw_array_wheremasked_assign_array(int ndim, npy_intp const *shape,
npy_intp wheremask_strides_it[NPY_MAXDIMS];
npy_intp coord[NPY_MAXDIMS];
- int aligned, needs_api = 0;
+ int aligned;
NPY_BEGIN_THREADS_DEF;
@@ -207,17 +223,21 @@ raw_array_wheremasked_assign_array(int ndim, npy_intp const *shape,
/* Get the function to do the casting */
NPY_cast_info cast_info;
+ NPY_ARRAYMETHOD_FLAGS flags;
if (PyArray_GetMaskedDTypeTransferFunction(aligned,
src_strides_it[0],
dst_strides_it[0],
wheremask_strides_it[0],
src_dtype, dst_dtype, wheremask_dtype,
0,
- &cast_info, &needs_api) != NPY_SUCCEED) {
+ &cast_info, &flags) != NPY_SUCCEED) {
return -1;
}
- if (!needs_api) {
+ if (!(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
+ npy_clear_floatstatus_barrier(src_data);
+ }
+ if (!(flags & NPY_METH_REQUIRES_PYAPI)) {
NPY_BEGIN_THREADS;
}
npy_intp strides[2] = {src_strides_it[0], dst_strides_it[0]};
@@ -232,7 +252,7 @@ raw_array_wheremasked_assign_array(int ndim, npy_intp const *shape,
args, &shape_it[0], strides,
(npy_bool *)wheremask_data, wheremask_strides_it[0],
cast_info.auxdata) < 0) {
- break;
+ goto fail;
}
} NPY_RAW_ITER_THREE_NEXT(idim, ndim, coord, shape_it,
dst_data, dst_strides_it,
@@ -241,7 +261,20 @@ raw_array_wheremasked_assign_array(int ndim, npy_intp const *shape,
NPY_END_THREADS;
NPY_cast_info_xfree(&cast_info);
- return (needs_api && PyErr_Occurred()) ? -1 : 0;
+
+ if (!(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
+ int fpes = npy_get_floatstatus_barrier(src_data);
+ if (fpes && PyUFunc_GiveFloatingpointErrors("cast", fpes) < 0) {
+ return -1;
+ }
+ }
+
+ return 0;
+
+fail:
+ NPY_END_THREADS;
+ NPY_cast_info_xfree(&cast_info);
+ return -1;
}
/*
diff --git a/numpy/core/src/multiarray/array_assign_scalar.c b/numpy/core/src/multiarray/array_assign_scalar.c
index 4ffef7ecc..ba964b86d 100644
--- a/numpy/core/src/multiarray/array_assign_scalar.c
+++ b/numpy/core/src/multiarray/array_assign_scalar.c
@@ -8,11 +8,13 @@
*/
#define NPY_NO_DEPRECATED_API NPY_API_VERSION
#define _MULTIARRAYMODULE
+#define _UMATHMODULE
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <numpy/ndarraytypes.h>
+#include "numpy/npy_math.h"
#include "npy_config.h"
#include "npy_pycompat.h"
@@ -25,6 +27,8 @@
#include "array_assign.h"
#include "dtype_transfer.h"
+#include "umathmodule.h"
+
/*
* Assigns the scalar value to every element of the destination raw array.
*
@@ -39,7 +43,7 @@ raw_array_assign_scalar(int ndim, npy_intp const *shape,
npy_intp shape_it[NPY_MAXDIMS], dst_strides_it[NPY_MAXDIMS];
npy_intp coord[NPY_MAXDIMS];
- int aligned, needs_api = 0;
+ int aligned;
NPY_BEGIN_THREADS_DEF;
@@ -62,15 +66,19 @@ raw_array_assign_scalar(int ndim, npy_intp const *shape,
/* Get the function to do the casting */
NPY_cast_info cast_info;
+ NPY_ARRAYMETHOD_FLAGS flags;
if (PyArray_GetDTypeTransferFunction(aligned,
0, dst_strides_it[0],
src_dtype, dst_dtype,
0,
- &cast_info, &needs_api) != NPY_SUCCEED) {
+ &cast_info, &flags) != NPY_SUCCEED) {
return -1;
}
- if (!needs_api) {
+ if (!(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
+ npy_clear_floatstatus_barrier(src_data);
+ }
+ if (!(flags & NPY_METH_REQUIRES_PYAPI)) {
npy_intp nitems = 1, i;
for (i = 0; i < ndim; i++) {
nitems *= shape_it[i];
@@ -92,6 +100,14 @@ raw_array_assign_scalar(int ndim, npy_intp const *shape,
NPY_END_THREADS;
NPY_cast_info_xfree(&cast_info);
+
+ if (!(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
+ int fpes = npy_get_floatstatus_barrier(src_data);
+ if (fpes && PyUFunc_GiveFloatingpointErrors("cast", fpes) < 0) {
+ return -1;
+ }
+ }
+
return 0;
fail:
NPY_END_THREADS;
@@ -117,7 +133,7 @@ raw_array_wheremasked_assign_scalar(int ndim, npy_intp const *shape,
npy_intp wheremask_strides_it[NPY_MAXDIMS];
npy_intp coord[NPY_MAXDIMS];
- int aligned, needs_api = 0;
+ int aligned;
NPY_BEGIN_THREADS_DEF;
@@ -142,15 +158,19 @@ raw_array_wheremasked_assign_scalar(int ndim, npy_intp const *shape,
/* Get the function to do the casting */
NPY_cast_info cast_info;
+ NPY_ARRAYMETHOD_FLAGS flags;
if (PyArray_GetMaskedDTypeTransferFunction(aligned,
0, dst_strides_it[0], wheremask_strides_it[0],
src_dtype, dst_dtype, wheremask_dtype,
0,
- &cast_info, &needs_api) != NPY_SUCCEED) {
+ &cast_info, &flags) != NPY_SUCCEED) {
return -1;
}
- if (!needs_api) {
+ if (!(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
+ npy_clear_floatstatus_barrier(src_data);
+ }
+ if (!(flags & NPY_METH_REQUIRES_PYAPI)) {
npy_intp nitems = 1, i;
for (i = 0; i < ndim; i++) {
nitems *= shape_it[i];
@@ -170,7 +190,7 @@ raw_array_wheremasked_assign_scalar(int ndim, npy_intp const *shape,
args, &shape_it[0], strides,
(npy_bool *)wheremask_data, wheremask_strides_it[0],
cast_info.auxdata) < 0) {
- break;
+ goto fail;
}
} NPY_RAW_ITER_TWO_NEXT(idim, ndim, coord, shape_it,
dst_data, dst_strides_it,
@@ -178,7 +198,20 @@ raw_array_wheremasked_assign_scalar(int ndim, npy_intp const *shape,
NPY_END_THREADS;
NPY_cast_info_xfree(&cast_info);
- return (needs_api && PyErr_Occurred()) ? -1 : 0;
+
+ if (!(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
+ int fpes = npy_get_floatstatus_barrier(src_data);
+ if (fpes && PyUFunc_GiveFloatingpointErrors("cast", fpes) < 0) {
+ return -1;
+ }
+ }
+
+ return 0;
+
+fail:
+ NPY_END_THREADS;
+ NPY_cast_info_xfree(&cast_info);
+ return -1;
}
/*
diff --git a/numpy/core/src/multiarray/array_coercion.c b/numpy/core/src/multiarray/array_coercion.c
index 0886d2d0b..e703e7382 100644
--- a/numpy/core/src/multiarray/array_coercion.c
+++ b/numpy/core/src/multiarray/array_coercion.c
@@ -9,6 +9,7 @@
#include "lowlevel_strided_loops.h"
#include "numpy/arrayobject.h"
+#include "numpy/npy_math.h"
#include "descriptor.h"
#include "convert_datatype.h"
@@ -22,6 +23,7 @@
#include "_datetime.h"
#include "npy_import.h"
+#include "umathmodule.h"
/*
* This file defines helpers for some of the ctors.c functions which
@@ -388,21 +390,36 @@ cast_raw_scalar_item(
PyArray_Descr *from_descr, char *from_item,
PyArray_Descr *to_descr, char *to_item)
{
- int needs_api = 0;
NPY_cast_info cast_info;
+ NPY_ARRAYMETHOD_FLAGS flags;
if (PyArray_GetDTypeTransferFunction(
0, 0, 0, from_descr, to_descr, 0, &cast_info,
- &needs_api) == NPY_FAIL) {
+ &flags) == NPY_FAIL) {
return -1;
}
+
+ if (!(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
+ npy_clear_floatstatus_barrier(from_item);
+ }
+
char *args[2] = {from_item, to_item};
const npy_intp strides[2] = {0, 0};
const npy_intp length = 1;
- int res = cast_info.func(&cast_info.context,
- args, &length, strides, cast_info.auxdata);
-
+ if (cast_info.func(&cast_info.context,
+ args, &length, strides, cast_info.auxdata) < 0) {
+ NPY_cast_info_xfree(&cast_info);
+ return -1;
+ }
NPY_cast_info_xfree(&cast_info);
- return res;
+
+ if (!(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
+ int fpes = npy_get_floatstatus_barrier(to_item);
+ if (fpes && PyUFunc_GiveFloatingpointErrors("cast", fpes) < 0) {
+ return -1;
+ }
+ }
+
+ return 0;
}
diff --git a/numpy/core/src/multiarray/array_method.h b/numpy/core/src/multiarray/array_method.h
index 6e6a026bc..c9ec8903d 100644
--- a/numpy/core/src/multiarray/array_method.h
+++ b/numpy/core/src/multiarray/array_method.h
@@ -20,7 +20,11 @@ typedef enum {
* setup/check. No function should set error flags and ignore them
* since it would interfere with chaining operations (e.g. casting).
*/
- /* TODO: Change this into a positive flag */
+ /*
+ * TODO: Change this into a positive flag? That would make "combing"
+ * multiple methods easier. OTOH, if we add more flags, the default
+ * would be 0 just like it is here.
+ */
NPY_METH_NO_FLOATINGPOINT_ERRORS = 1 << 2,
/* Whether the method supports unaligned access (not runtime) */
NPY_METH_SUPPORTS_UNALIGNED = 1 << 3,
@@ -43,6 +47,20 @@ typedef enum {
} NPY_ARRAYMETHOD_FLAGS;
+/*
+ * It would be nice to just | flags, but in general it seems that 0 bits
+ * probably should indicate "default".
+ * And that is not necessarily compatible with `|`.
+ *
+ * NOTE: If made public, should maybe be a function to easier add flags?
+ */
+#define PyArrayMethod_MINIMAL_FLAGS NPY_METH_NO_FLOATINGPOINT_ERRORS
+#define PyArrayMethod_COMBINED_FLAGS(flags1, flags2) \
+ ((NPY_ARRAYMETHOD_FLAGS)( \
+ ((flags1 | flags2) & ~PyArrayMethod_MINIMAL_FLAGS) \
+ | (flags1 & flags2)))
+
+
struct PyArrayMethodObject_tag;
/*
diff --git a/numpy/core/src/multiarray/arraytypes.c.src b/numpy/core/src/multiarray/arraytypes.c.src
index ee4f5f312..a9f8dfdd2 100644
--- a/numpy/core/src/multiarray/arraytypes.c.src
+++ b/numpy/core/src/multiarray/arraytypes.c.src
@@ -7,6 +7,7 @@
#define NPY_NO_DEPRECATED_API NPY_API_VERSION
#define _MULTIARRAYMODULE
+#define _UMATHMODULE
#define _NPY_NO_DEPRECATIONS /* for NPY_CHAR */
#include "numpy/npy_common.h"
@@ -37,6 +38,9 @@
#include "npy_buffer.h"
#include "arraytypes.h"
+
+#include "umathmodule.h"
+
/*
* Define a stack allocated dummy array with only the minimum information set:
* 1. The descr, the main field interesting here.
@@ -96,10 +100,32 @@ MyPyFloat_AsDouble(PyObject *obj)
return ret;
}
+
+static float
+MyPyFloat_AsFloat(PyObject *obj)
+{
+ double d_val = MyPyFloat_AsDouble(obj);
+ float res = (float)d_val;
+ if (NPY_UNLIKELY(npy_isinf(res) && !npy_isinf(d_val))) {
+ if (PyUFunc_GiveFloatingpointErrors("cast", NPY_FPE_OVERFLOW) < 0) {
+ return -1;
+ }
+ }
+ return res;
+}
+
+
static npy_half
MyPyFloat_AsHalf(PyObject *obj)
{
- return npy_double_to_half(MyPyFloat_AsDouble(obj));
+ double d_val = MyPyFloat_AsDouble(obj);
+ npy_half res = npy_double_to_half(d_val);
+ if (NPY_UNLIKELY(npy_half_isinf(res) && !npy_isinf(d_val))) {
+ if (PyUFunc_GiveFloatingpointErrors("cast", NPY_FPE_OVERFLOW) < 0) {
+ return npy_double_to_half(-1.);
+ }
+ }
+ return res;
}
static PyObject *
@@ -200,7 +226,7 @@ MyPyLong_AsUnsigned@Type@ (PyObject *obj)
* MyPyFloat_FromHalf, PyFloat_FromDouble*2#
* #func2 = PyObject_IsTrue, MyPyLong_AsLong*6, MyPyLong_AsUnsignedLong*2,
* MyPyLong_AsLongLong, MyPyLong_AsUnsignedLongLong,
- * MyPyFloat_AsHalf, MyPyFloat_AsDouble*2#
+ * MyPyFloat_AsHalf, MyPyFloat_AsFloat, MyPyFloat_AsDouble#
* #type = npy_bool,
* npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int,
* npy_long, npy_uint, npy_ulong, npy_longlong, npy_ulonglong,
@@ -363,6 +389,26 @@ static int
}
temp.real = (@ftype@) oop.real;
temp.imag = (@ftype@) oop.imag;
+
+#if NPY_SIZEOF_@NAME@ < NPY_SIZEOF_CDOUBLE /* really just float... */
+ /* Overflow could have occured converting double to float */
+ if (NPY_UNLIKELY((npy_isinf(temp.real) && !npy_isinf(oop.real)) ||
+ (npy_isinf(temp.imag) && !npy_isinf(oop.imag)))) {
+ int bufsize, errmask;
+ PyObject *errobj;
+
+ if (PyUFunc_GetPyValues("assignment", &bufsize, &errmask,
+ &errobj) < 0) {
+ return -1;
+ }
+ int first = 1;
+ if (PyUFunc_handlefperr(errmask, errobj, NPY_FPE_OVERFLOW, &first)) {
+ Py_XDECREF(errobj);
+ return -1;
+ }
+ Py_XDECREF(errobj);
+ }
+#endif
}
memcpy(ov, &temp, PyArray_DESCR(ap)->elsize);
@@ -1151,13 +1197,22 @@ static void
@totype@ *op = output;
while (n--) {
- @fromtype@ f = *ip++;
- @totype@ t = (@totype@)f;
#if @supports_nat@ && @floatingpoint@
- /* Avoid undefined behaviour for NaN -> NaT */
+ /*
+ * volatile works around clang (and gcc sometimes) not branching
+ * correctly, leading to floating point errors in the test suite.
+ */
+ volatile @fromtype@ f = *ip++;
+ @totype@ t;
+ /* Avoid undefined behaviour and warning for NaN -> NaT */
if (npy_isnan(f)) {
t = (@totype@)NPY_DATETIME_NAT;
}
+ else {
+ t = (@totype@)f;
+ }
+#else
+ @totype@ t = (@totype@)*ip++;
#endif
*op++ = t;
}
@@ -1177,13 +1232,22 @@ static void
@totype@ *op = output;
while (n--) {
- @fromtype@ f = *ip;
- @totype@ t = (@totype@)f;
#if @supports_nat@
- /* Avoid undefined behaviour for NaN -> NaT */
+ /*
+ * volatile works around clang (and gcc sometimes) not branching
+ * correctly, leading to floating point errors in the test suite.
+ */
+ volatile @fromtype@ f = *ip;
+ @totype@ t;
+ /* Avoid undefined behaviour and warning for NaN -> NaT */
if (npy_isnan(f)) {
t = (@totype@)NPY_DATETIME_NAT;
}
+ else {
+ t = (@totype@)f;
+ }
+#else
+ @totype@ t = (@totype@)*ip;
#endif
*op++ = t;
ip += 2;
diff --git a/numpy/core/src/multiarray/convert_datatype.c b/numpy/core/src/multiarray/convert_datatype.c
index 8d0a4cd56..ce2c73a7a 100644
--- a/numpy/core/src/multiarray/convert_datatype.c
+++ b/numpy/core/src/multiarray/convert_datatype.c
@@ -3042,26 +3042,22 @@ nonstructured_to_structured_get_loop(
NPY_ARRAYMETHOD_FLAGS *flags)
{
if (context->descriptors[1]->names != NULL) {
- int needs_api = 0;
if (get_fields_transfer_function(
aligned, strides[0], strides[1],
context->descriptors[0], context->descriptors[1],
move_references, out_loop, out_transferdata,
- &needs_api) == NPY_FAIL) {
+ flags) == NPY_FAIL) {
return -1;
}
- *flags = needs_api ? NPY_METH_REQUIRES_PYAPI : 0;
}
else if (context->descriptors[1]->subarray != NULL) {
- int needs_api = 0;
if (get_subarray_transfer_function(
aligned, strides[0], strides[1],
context->descriptors[0], context->descriptors[1],
move_references, out_loop, out_transferdata,
- &needs_api) == NPY_FAIL) {
+ flags) == NPY_FAIL) {
return -1;
}
- *flags = needs_api ? NPY_METH_REQUIRES_PYAPI : 0;
}
else {
/*
@@ -3204,26 +3200,22 @@ structured_to_nonstructured_get_loop(
NPY_ARRAYMETHOD_FLAGS *flags)
{
if (context->descriptors[0]->names != NULL) {
- int needs_api = 0;
if (get_fields_transfer_function(
aligned, strides[0], strides[1],
context->descriptors[0], context->descriptors[1],
move_references, out_loop, out_transferdata,
- &needs_api) == NPY_FAIL) {
+ flags) == NPY_FAIL) {
return -1;
}
- *flags = needs_api ? NPY_METH_REQUIRES_PYAPI : 0;
}
else if (context->descriptors[0]->subarray != NULL) {
- int needs_api = 0;
if (get_subarray_transfer_function(
aligned, strides[0], strides[1],
context->descriptors[0], context->descriptors[1],
move_references, out_loop, out_transferdata,
- &needs_api) == NPY_FAIL) {
+ flags) == NPY_FAIL) {
return -1;
}
- *flags = needs_api ? NPY_METH_REQUIRES_PYAPI : 0;
}
else {
/*
@@ -3513,27 +3505,23 @@ void_to_void_get_loop(
{
if (context->descriptors[0]->names != NULL ||
context->descriptors[1]->names != NULL) {
- int needs_api = 0;
if (get_fields_transfer_function(
aligned, strides[0], strides[1],
context->descriptors[0], context->descriptors[1],
move_references, out_loop, out_transferdata,
- &needs_api) == NPY_FAIL) {
+ flags) == NPY_FAIL) {
return -1;
}
- *flags = needs_api ? NPY_METH_REQUIRES_PYAPI : 0;
}
else if (context->descriptors[0]->subarray != NULL ||
context->descriptors[1]->subarray != NULL) {
- int needs_api = 0;
if (get_subarray_transfer_function(
aligned, strides[0], strides[1],
context->descriptors[0], context->descriptors[1],
move_references, out_loop, out_transferdata,
- &needs_api) == NPY_FAIL) {
+ flags) == NPY_FAIL) {
return -1;
}
- *flags = needs_api ? NPY_METH_REQUIRES_PYAPI : 0;
}
else {
/*
@@ -3546,7 +3534,7 @@ void_to_void_get_loop(
out_loop, out_transferdata) == NPY_FAIL) {
return -1;
}
- *flags = 0;
+ *flags = PyArrayMethod_MINIMAL_FLAGS;
}
return 0;
}
diff --git a/numpy/core/src/multiarray/ctors.c b/numpy/core/src/multiarray/ctors.c
index 95a3bd84d..c3d66dd6b 100644
--- a/numpy/core/src/multiarray/ctors.c
+++ b/numpy/core/src/multiarray/ctors.c
@@ -1,5 +1,6 @@
#define NPY_NO_DEPRECATED_API NPY_API_VERSION
#define _MULTIARRAYMODULE
+#define _UMATHMODULE
#define PY_SSIZE_T_CLEAN
#include <Python.h>
@@ -33,6 +34,8 @@
#include "get_attr_string.h"
#include "array_coercion.h"
+#include "umathmodule.h"
+
/*
* Reading from a file or a string.
*
@@ -2741,18 +2744,22 @@ PyArray_CopyAsFlat(PyArrayObject *dst, PyArrayObject *src, NPY_ORDER order)
* contiguous strides, etc.
*/
NPY_cast_info cast_info;
+ NPY_ARRAYMETHOD_FLAGS flags;
if (PyArray_GetDTypeTransferFunction(
IsUintAligned(src) && IsAligned(src) &&
IsUintAligned(dst) && IsAligned(dst),
src_stride, dst_stride,
PyArray_DESCR(src), PyArray_DESCR(dst),
0,
- &cast_info, &needs_api) != NPY_SUCCEED) {
+ &cast_info, &flags) != NPY_SUCCEED) {
NpyIter_Deallocate(dst_iter);
NpyIter_Deallocate(src_iter);
return -1;
}
-
+ needs_api |= (flags & NPY_METH_REQUIRES_PYAPI) != 0;
+ if (!(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
+ npy_clear_floatstatus_barrier((char *)src_iter);
+ }
if (!needs_api) {
NPY_BEGIN_THREADS;
}
@@ -2804,8 +2811,20 @@ PyArray_CopyAsFlat(PyArrayObject *dst, PyArrayObject *src, NPY_ORDER order)
NPY_END_THREADS;
NPY_cast_info_xfree(&cast_info);
- NpyIter_Deallocate(dst_iter);
- NpyIter_Deallocate(src_iter);
+ if (!NpyIter_Deallocate(dst_iter)) {
+ res = -1;
+ }
+ if (!NpyIter_Deallocate(src_iter)) {
+ res = -1;
+ }
+
+ if (res == 0 && !(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
+ int fpes = npy_get_floatstatus_barrier((char *)src_iter);
+ if (fpes && PyUFunc_GiveFloatingpointErrors("cast", fpes) < 0) {
+ return -1;
+ }
+ }
+
return res;
}
diff --git a/numpy/core/src/multiarray/dtype_transfer.c b/numpy/core/src/multiarray/dtype_transfer.c
index 18de5d132..f8458d2d7 100644
--- a/numpy/core/src/multiarray/dtype_transfer.c
+++ b/numpy/core/src/multiarray/dtype_transfer.c
@@ -11,12 +11,14 @@
*/
#define NPY_NO_DEPRECATED_API NPY_API_VERSION
#define _MULTIARRAYMODULE
+#define _UMATHMODULE
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <structmember.h>
#include "numpy/arrayobject.h"
+#include "numpy/npy_math.h"
#include "lowlevel_strided_loops.h"
#include "npy_pycompat.h"
@@ -35,6 +37,8 @@
#include "array_method.h"
#include "array_coercion.h"
+#include "umathmodule.h"
+
#define NPY_LOWLEVEL_BUFFER_BLOCKSIZE 128
/********** PRINTF DEBUG TRACING **************/
@@ -1506,7 +1510,7 @@ get_one_to_n_transfer_function(int aligned,
npy_intp N,
PyArrayMethod_StridedLoop **out_stransfer,
NpyAuxData **out_transferdata,
- int *out_needs_api)
+ NPY_ARRAYMETHOD_FLAGS *out_flags)
{
_one_to_n_data *data = PyMem_Malloc(sizeof(_one_to_n_data));
if (data == NULL) {
@@ -1530,18 +1534,19 @@ get_one_to_n_transfer_function(int aligned,
src_dtype, dst_dtype,
0,
&data->wrapped,
- out_needs_api) != NPY_SUCCEED) {
+ out_flags) != NPY_SUCCEED) {
NPY_AUXDATA_FREE((NpyAuxData *)data);
return NPY_FAIL;
}
/* If the src object will need a DECREF, set src_dtype */
if (move_references && PyDataType_REFCHK(src_dtype)) {
+ *out_flags |= NPY_METH_REQUIRES_PYAPI;
if (get_decref_transfer_function(aligned,
src_stride,
src_dtype,
&data->decref_src,
- out_needs_api) != NPY_SUCCEED) {
+ NULL) != NPY_SUCCEED) {
NPY_AUXDATA_FREE((NpyAuxData *)data);
return NPY_FAIL;
}
@@ -1667,7 +1672,7 @@ get_n_to_n_transfer_function(int aligned,
npy_intp N,
PyArrayMethod_StridedLoop **out_stransfer,
NpyAuxData **out_transferdata,
- int *out_needs_api)
+ NPY_ARRAYMETHOD_FLAGS *out_flags)
{
_n_to_n_data *data = PyMem_Malloc(sizeof(_n_to_n_data));
if (data == NULL) {
@@ -1699,7 +1704,7 @@ get_n_to_n_transfer_function(int aligned,
src_dtype, dst_dtype,
move_references,
&data->wrapped,
- out_needs_api) != NPY_SUCCEED) {
+ out_flags) != NPY_SUCCEED) {
NPY_AUXDATA_FREE((NpyAuxData *)data);
return NPY_FAIL;
}
@@ -1913,7 +1918,7 @@ get_subarray_broadcast_transfer_function(int aligned,
int move_references,
PyArrayMethod_StridedLoop **out_stransfer,
NpyAuxData **out_transferdata,
- int *out_needs_api)
+ NPY_ARRAYMETHOD_FLAGS *out_flags)
{
_subarray_broadcast_data *data;
npy_intp structsize, loop_index, run, run_size,
@@ -1946,7 +1951,7 @@ get_subarray_broadcast_transfer_function(int aligned,
src_dtype, dst_dtype,
0,
&data->wrapped,
- out_needs_api) != NPY_SUCCEED) {
+ out_flags) != NPY_SUCCEED) {
NPY_AUXDATA_FREE((NpyAuxData *)data);
return NPY_FAIL;
}
@@ -1958,7 +1963,7 @@ get_subarray_broadcast_transfer_function(int aligned,
src_dtype, NULL,
1,
&data->decref_src,
- out_needs_api) != NPY_SUCCEED) {
+ out_flags) != NPY_SUCCEED) {
NPY_AUXDATA_FREE((NpyAuxData *)data);
return NPY_FAIL;
}
@@ -1971,7 +1976,7 @@ get_subarray_broadcast_transfer_function(int aligned,
dst_dtype, NULL,
1,
&data->decref_dst,
- out_needs_api) != NPY_SUCCEED) {
+ out_flags) != NPY_SUCCEED) {
NPY_AUXDATA_FREE((NpyAuxData *)data);
return NPY_FAIL;
}
@@ -2087,7 +2092,7 @@ get_subarray_transfer_function(int aligned,
int move_references,
PyArrayMethod_StridedLoop **out_stransfer,
NpyAuxData **out_transferdata,
- int *out_needs_api)
+ NPY_ARRAYMETHOD_FLAGS *out_flags)
{
PyArray_Dims src_shape = {NULL, -1}, dst_shape = {NULL, -1};
npy_intp src_size = 1, dst_size = 1;
@@ -2132,7 +2137,7 @@ get_subarray_transfer_function(int aligned,
move_references,
src_size,
out_stransfer, out_transferdata,
- out_needs_api);
+ out_flags);
}
/* Copy the src value to all the dst values */
else if (src_size == 1) {
@@ -2145,7 +2150,7 @@ get_subarray_transfer_function(int aligned,
move_references,
dst_size,
out_stransfer, out_transferdata,
- out_needs_api);
+ out_flags);
}
/*
* Copy the subarray with broadcasting, truncating, and zero-padding
@@ -2159,7 +2164,7 @@ get_subarray_transfer_function(int aligned,
src_shape, dst_shape,
move_references,
out_stransfer, out_transferdata,
- out_needs_api);
+ out_flags);
npy_free_cache_dim_obj(src_shape);
npy_free_cache_dim_obj(dst_shape);
@@ -2277,7 +2282,7 @@ get_fields_transfer_function(int NPY_UNUSED(aligned),
int move_references,
PyArrayMethod_StridedLoop **out_stransfer,
NpyAuxData **out_transferdata,
- int *out_needs_api)
+ NPY_ARRAYMETHOD_FLAGS *out_flags)
{
PyObject *key, *tup, *title;
PyArray_Descr *src_fld_dtype, *dst_fld_dtype;
@@ -2308,6 +2313,7 @@ get_fields_transfer_function(int NPY_UNUSED(aligned),
data->base.clone = &_field_transfer_data_clone;
data->field_count = 0;
+ *out_flags = PyArrayMethod_MINIMAL_FLAGS;
for (i = 0; i < field_count; ++i) {
key = PyTuple_GET_ITEM(dst_dtype->names, i);
tup = PyDict_GetItem(dst_dtype->fields, key);
@@ -2316,15 +2322,17 @@ get_fields_transfer_function(int NPY_UNUSED(aligned),
PyMem_Free(data);
return NPY_FAIL;
}
+ NPY_ARRAYMETHOD_FLAGS field_flags;
if (PyArray_GetDTypeTransferFunction(0,
src_stride, dst_stride,
src_dtype, dst_fld_dtype,
0,
&data->fields[i].info,
- out_needs_api) != NPY_SUCCEED) {
+ &field_flags) != NPY_SUCCEED) {
NPY_AUXDATA_FREE((NpyAuxData *)data);
return NPY_FAIL;
}
+ *out_flags = PyArrayMethod_COMBINED_FLAGS(*out_flags, field_flags);
data->fields[i].src_offset = 0;
data->fields[i].dst_offset = dst_offset;
data->field_count++;
@@ -2336,11 +2344,12 @@ get_fields_transfer_function(int NPY_UNUSED(aligned),
* input, the second one (normally output) just does not matter here.
*/
if (move_references && PyDataType_REFCHK(src_dtype)) {
+ *out_flags |= NPY_METH_REQUIRES_PYAPI;
if (get_decref_transfer_function(0,
src_stride,
src_dtype,
&data->fields[field_count].info,
- out_needs_api) != NPY_SUCCEED) {
+ NULL) != NPY_SUCCEED) {
NPY_AUXDATA_FREE((NpyAuxData *)data);
return NPY_FAIL;
}
@@ -2388,7 +2397,7 @@ get_fields_transfer_function(int NPY_UNUSED(aligned),
src_fld_dtype, dst_dtype,
move_references,
&data->fields[0].info,
- out_needs_api) != NPY_SUCCEED) {
+ out_flags) != NPY_SUCCEED) {
PyMem_Free(data);
return NPY_FAIL;
}
@@ -2423,6 +2432,7 @@ get_fields_transfer_function(int NPY_UNUSED(aligned),
data->base.clone = &_field_transfer_data_clone;
data->field_count = 0;
+ *out_flags = PyArrayMethod_MINIMAL_FLAGS;
/* set up the transfer function for each field */
for (i = 0; i < field_count; ++i) {
key = PyTuple_GET_ITEM(dst_dtype->names, i);
@@ -2440,15 +2450,17 @@ get_fields_transfer_function(int NPY_UNUSED(aligned),
return NPY_FAIL;
}
+ NPY_ARRAYMETHOD_FLAGS field_flags;
if (PyArray_GetDTypeTransferFunction(0,
src_stride, dst_stride,
src_fld_dtype, dst_fld_dtype,
move_references,
&data->fields[i].info,
- out_needs_api) != NPY_SUCCEED) {
+ &field_flags) != NPY_SUCCEED) {
NPY_AUXDATA_FREE((NpyAuxData *)data);
return NPY_FAIL;
}
+ *out_flags = PyArrayMethod_COMBINED_FLAGS(*out_flags, field_flags);
data->fields[i].src_offset = src_offset;
data->fields[i].dst_offset = dst_offset;
data->field_count++;
@@ -2748,11 +2760,12 @@ get_decref_transfer_function(int aligned,
src_size = PyArray_MultiplyList(src_shape.ptr, src_shape.len);
npy_free_cache_dim_obj(src_shape);
+ NPY_ARRAYMETHOD_FLAGS ignored_flags;
if (get_n_to_n_transfer_function(aligned,
src_stride, 0,
src_dtype->subarray->base, NULL, 1, src_size,
&cast_info->func, &cast_info->auxdata,
- out_needs_api) != NPY_SUCCEED) {
+ &ignored_flags) != NPY_SUCCEED) {
return NPY_FAIL;
}
@@ -3098,7 +3111,7 @@ define_cast_for_descrs(
npy_intp src_stride, npy_intp dst_stride,
PyArray_Descr *src_dtype, PyArray_Descr *dst_dtype,
int move_references,
- NPY_cast_info *cast_info, int *out_needs_api)
+ NPY_cast_info *cast_info, NPY_ARRAYMETHOD_FLAGS *out_flags)
{
/* Storage for all cast info in case multi-step casting is necessary */
_multistep_castdata castdata;
@@ -3109,6 +3122,7 @@ define_cast_for_descrs(
/* `view_offset` passed to `init_cast_info` but unused for the main cast */
npy_intp view_offset = NPY_MIN_INTP;
NPY_CASTING casting = -1;
+ *out_flags = PyArrayMethod_MINIMAL_FLAGS;
if (init_cast_info(
cast_info, &casting, &view_offset, src_dtype, dst_dtype, 1) < 0) {
@@ -3159,7 +3173,7 @@ define_cast_for_descrs(
}
assert(castdata.from.func != NULL);
- *out_needs_api |= (flags & NPY_METH_REQUIRES_PYAPI) != 0;
+ *out_flags = PyArrayMethod_COMBINED_FLAGS(*out_flags, flags);
/* The main cast now uses a buffered input: */
src_stride = strides[1];
move_references = 1; /* main cast has to clear the buffer */
@@ -3198,7 +3212,7 @@ define_cast_for_descrs(
}
assert(castdata.to.func != NULL);
- *out_needs_api |= (flags & NPY_METH_REQUIRES_PYAPI) != 0;
+ *out_flags = PyArrayMethod_COMBINED_FLAGS(*out_flags, flags);
/* The main cast now uses a buffered input: */
dst_stride = strides[0];
if (castdata.from.func != NULL) {
@@ -3219,7 +3233,7 @@ define_cast_for_descrs(
goto fail;
}
- *out_needs_api |= (flags & NPY_METH_REQUIRES_PYAPI) != 0;
+ *out_flags = PyArrayMethod_COMBINED_FLAGS(*out_flags, flags);
if (castdata.from.func == NULL && castdata.to.func == NULL) {
/* Most of the time, there will be only one step required. */
@@ -3256,7 +3270,7 @@ PyArray_GetDTypeTransferFunction(int aligned,
PyArray_Descr *src_dtype, PyArray_Descr *dst_dtype,
int move_references,
NPY_cast_info *cast_info,
- int *out_needs_api)
+ NPY_ARRAYMETHOD_FLAGS *out_flags)
{
assert(src_dtype != NULL);
@@ -3271,17 +3285,24 @@ PyArray_GetDTypeTransferFunction(int aligned,
*/
if (dst_dtype == NULL) {
assert(move_references);
- return get_decref_transfer_function(aligned,
+ int needs_api = 0;
+ int res = get_decref_transfer_function(aligned,
src_dtype->elsize,
src_dtype,
cast_info,
- out_needs_api);
+ &needs_api);
+ /* decref'ing never creates floating point errors, so just ignore it */
+ *out_flags = PyArrayMethod_MINIMAL_FLAGS;
+ if (needs_api) {
+ *out_flags |= NPY_METH_REQUIRES_PYAPI;
+ }
+ return res;
}
if (define_cast_for_descrs(aligned,
src_stride, dst_stride,
src_dtype, dst_dtype, move_references,
- cast_info, out_needs_api) < 0) {
+ cast_info, out_flags) < 0) {
return NPY_FAIL;
}
@@ -3353,21 +3374,29 @@ wrap_aligned_transferfunction(
* have an explicit implementation instead if we want performance.
*/
if (must_wrap || src_wrapped_dtype != src_dtype) {
+ NPY_ARRAYMETHOD_FLAGS flags;
if (PyArray_GetDTypeTransferFunction(aligned,
src_stride, castdata.main.descriptors[0]->elsize,
src_dtype, castdata.main.descriptors[0], 0,
- &castdata.from, out_needs_api) != NPY_SUCCEED) {
+ &castdata.from, &flags) != NPY_SUCCEED) {
goto fail;
}
+ if (flags & NPY_METH_REQUIRES_PYAPI) {
+ *out_needs_api = 1;
+ }
}
if (must_wrap || dst_wrapped_dtype != dst_dtype) {
+ NPY_ARRAYMETHOD_FLAGS flags;
if (PyArray_GetDTypeTransferFunction(aligned,
castdata.main.descriptors[1]->elsize, dst_stride,
castdata.main.descriptors[1], dst_dtype,
1, /* clear buffer if it includes references */
- &castdata.to, out_needs_api) != NPY_SUCCEED) {
+ &castdata.to, &flags) != NPY_SUCCEED) {
goto fail;
}
+ if (flags & NPY_METH_REQUIRES_PYAPI) {
+ *out_needs_api = 1;
+ }
}
*out_transferdata = _multistep_cast_auxdata_clone_int(&castdata, 1);
@@ -3492,7 +3521,7 @@ PyArray_GetMaskedDTypeTransferFunction(int aligned,
PyArray_Descr *mask_dtype,
int move_references,
NPY_cast_info *cast_info,
- int *out_needs_api)
+ NPY_ARRAYMETHOD_FLAGS *out_flags)
{
NPY_cast_info_init(cast_info);
@@ -3520,18 +3549,19 @@ PyArray_GetMaskedDTypeTransferFunction(int aligned,
src_dtype, dst_dtype,
move_references,
&data->wrapped,
- out_needs_api) != NPY_SUCCEED) {
+ out_flags) != NPY_SUCCEED) {
PyMem_Free(data);
return NPY_FAIL;
}
/* If the src object will need a DECREF, get a function to handle that */
if (move_references && PyDataType_REFCHK(src_dtype)) {
+ *out_flags |= NPY_METH_REQUIRES_PYAPI;
if (get_decref_transfer_function(aligned,
src_stride,
src_dtype,
&data->decref_src,
- out_needs_api) != NPY_SUCCEED) {
+ NULL) != NPY_SUCCEED) {
NPY_AUXDATA_FREE((NpyAuxData *)data);
return NPY_FAIL;
}
@@ -3562,7 +3592,7 @@ PyArray_CastRawArrays(npy_intp count,
PyArray_Descr *src_dtype, PyArray_Descr *dst_dtype,
int move_references)
{
- int aligned = 1, needs_api = 0;
+ int aligned;
/* Make sure the copy is reasonable */
if (dst_stride == 0 && count > 1) {
@@ -3586,15 +3616,20 @@ PyArray_CastRawArrays(npy_intp count,
/* Get the function to do the casting */
NPY_cast_info cast_info;
+ NPY_ARRAYMETHOD_FLAGS flags;
if (PyArray_GetDTypeTransferFunction(aligned,
src_stride, dst_stride,
src_dtype, dst_dtype,
move_references,
&cast_info,
- &needs_api) != NPY_SUCCEED) {
+ &flags) != NPY_SUCCEED) {
return NPY_FAIL;
}
+ if (!(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
+ npy_clear_floatstatus_barrier((char*)&cast_info);
+ }
+
/* Cast */
char *args[2] = {src, dst};
npy_intp strides[2] = {src_stride, dst_stride};
@@ -3603,8 +3638,16 @@ PyArray_CastRawArrays(npy_intp count,
/* Cleanup */
NPY_cast_info_xfree(&cast_info);
- /* If needs_api was set to 1, it may have raised a Python exception */
- return (needs_api && PyErr_Occurred()) ? NPY_FAIL : NPY_SUCCEED;
+ if (flags & NPY_METH_REQUIRES_PYAPI && PyErr_Occurred()) {
+ return NPY_FAIL;
+ }
+ if (!(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
+ int fpes = npy_get_floatstatus_barrier(*args);
+ if (fpes && PyUFunc_GiveFloatingpointErrors("cast", fpes) < 0) {
+ return NPY_FAIL;
+ }
+ }
+ return NPY_SUCCEED;
}
/*
diff --git a/numpy/core/src/multiarray/lowlevel_strided_loops.c.src b/numpy/core/src/multiarray/lowlevel_strided_loops.c.src
index e313d2447..8e3afd3cc 100644
--- a/numpy/core/src/multiarray/lowlevel_strided_loops.c.src
+++ b/numpy/core/src/multiarray/lowlevel_strided_loops.c.src
@@ -13,6 +13,7 @@
#define NPY_NO_DEPRECATED_API NPY_API_VERSION
#define _MULTIARRAYMODULE
+#define _UMATHMODULE
#include <numpy/arrayobject.h>
#include <numpy/npy_cpu.h>
#include <numpy/halffloat.h>
@@ -22,6 +23,7 @@
#include "array_method.h"
#include "usertypes.h"
+#include "umathmodule.h"
/*
* x86 platform works with unaligned access but the compiler is allowed to
@@ -1557,14 +1559,16 @@ mapiter_trivial_@name@(PyArrayObject *self, PyArrayObject *ind,
* General advanced indexing iteration.
*/
NPY_NO_EXPORT int
-mapiter_@name@(PyArrayMapIterObject *mit)
+mapiter_@name@(
+ PyArrayMapIterObject *mit, NPY_cast_info *cast_info,
+ NPY_ARRAYMETHOD_FLAGS flags, int is_aligned)
{
npy_intp *counter, count;
- int i, is_aligned;
+ int i;
/* Cached mit info */
int numiter = mit->numiter;
- int needs_api = mit->needs_api;
+ int needs_api = (flags & NPY_METH_REQUIRES_PYAPI) != 0;
/* Constant information */
npy_intp fancy_dims[NPY_MAXDIMS];
npy_intp fancy_strides[NPY_MAXDIMS];
@@ -1586,13 +1590,6 @@ mapiter_@name@(PyArrayMapIterObject *mit)
fancy_strides[i] = mit->fancy_strides[i];
}
- /*
- * Alignment information (swapping is never needed, since we buffer),
- * could also check extra_op is buffered, but it should rarely matter.
- */
-
- is_aligned = IsUintAligned(array) && IsUintAligned(mit->extra_op);
-
if (mit->size == 0) {
return 0;
}
@@ -1600,9 +1597,11 @@ mapiter_@name@(PyArrayMapIterObject *mit)
if (mit->subspace_iter == NULL) {
/*
* Item by item copy situation, the operand is buffered
- * so use copyswap.
+ * so use copyswap. The iterator may not do any transfers, so may
+ * not have set `needs_api` yet, set it if necessary:
*/
- PyArray_CopySwapFunc *copyswap = PyArray_DESCR(array)->f->copyswap;
+ needs_api |= PyDataType_REFCHK(PyArray_DESCR(array));
+ PyArray_CopySwapFunc *copyswap = PyArray_DESCR(array)->f->copyswap;
/* We have only one iterator handling everything */
counter = NpyIter_GetInnerLoopSizePtr(mit->outer);
@@ -1715,28 +1714,9 @@ mapiter_@name@(PyArrayMapIterObject *mit)
int is_subiter_trivial = 0; /* has three states */
npy_intp reset_offsets[2] = {0, 0};
- /* Use strided transfer functions for the inner loop */
- npy_intp fixed_strides[2];
-
- /*
- * Get a dtype transfer function, since there are no
- * buffers, this is safe.
- */
- NpyIter_GetInnerFixedStrideArray(mit->subspace_iter, fixed_strides);
-
- NPY_cast_info cast_info;
- if (PyArray_GetDTypeTransferFunction(is_aligned,
-#if @isget@
- fixed_strides[0], fixed_strides[1],
- PyArray_DESCR(array), PyArray_DESCR(mit->extra_op),
-#else
- fixed_strides[1], fixed_strides[0],
- PyArray_DESCR(mit->extra_op), PyArray_DESCR(array),
-#endif
- 0,
- &cast_info,
- &needs_api) != NPY_SUCCEED) {
- return -1;
+ /* Note: it may make sense to refactor `needs_api` out in this branch */
+ if (flags & NPY_METH_REQUIRES_PYAPI) {
+ needs_api = 1;
}
counter = NpyIter_GetInnerLoopSizePtr(mit->subspace_iter);
@@ -1771,7 +1751,6 @@ mapiter_@name@(PyArrayMapIterObject *mit)
#if @isget@ && @one_iter@
if (check_and_adjust_index(&indval, fancy_dims[i],
iteraxis, _save) < 0 ) {
- NPY_cast_info_xfree(&cast_info);
return -1;
}
#else
@@ -1803,7 +1782,6 @@ mapiter_@name@(PyArrayMapIterObject *mit)
&errmsg)) {
NPY_END_THREADS;
PyErr_SetString(PyExc_ValueError, errmsg);
- NPY_cast_info_xfree(&cast_info);
return -1;
}
if (is_subiter_trivial != 0) {
@@ -1833,7 +1811,6 @@ mapiter_@name@(PyArrayMapIterObject *mit)
* not at all...
*/
if (needs_api && PyErr_Occurred()) {
- NPY_cast_info_xfree(&cast_info);
return -1;
}
#endif
@@ -1841,21 +1818,19 @@ mapiter_@name@(PyArrayMapIterObject *mit)
do {
#if @isget@
- if (NPY_UNLIKELY(cast_info.func(&cast_info.context,
+ if (NPY_UNLIKELY(cast_info->func(&cast_info->context,
subspace_ptrs, counter, subspace_strides,
- cast_info.auxdata) < 0)) {
+ cast_info->auxdata) < 0)) {
NPY_END_THREADS;
- NPY_cast_info_xfree(&cast_info);
return -1;
}
#else
/* The operand order is reversed here */
char *args[2] = {subspace_ptrs[1], subspace_ptrs[0]};
npy_intp strides[2] = {subspace_strides[1], subspace_strides[0]};
- if (NPY_UNLIKELY(cast_info.func(&cast_info.context,
- args, counter, strides, cast_info.auxdata) < 0)) {
+ if (NPY_UNLIKELY(cast_info->func(&cast_info->context,
+ args, counter, strides, cast_info->auxdata) < 0)) {
NPY_END_THREADS;
- NPY_cast_info_xfree(&cast_info);
return -1;
}
#endif
@@ -1866,8 +1841,6 @@ mapiter_@name@(PyArrayMapIterObject *mit)
NPY_END_THREADS;
}
/**end repeat1**/
-
- NPY_cast_info_xfree(&cast_info);
}
return 0;
}
diff --git a/numpy/core/src/multiarray/mapping.c b/numpy/core/src/multiarray/mapping.c
index 1a2ade11b..98c2d7eda 100644
--- a/numpy/core/src/multiarray/mapping.c
+++ b/numpy/core/src/multiarray/mapping.c
@@ -1,11 +1,14 @@
#define NPY_NO_DEPRECATED_API NPY_API_VERSION
#define _MULTIARRAYMODULE
+#define _UMATHMODULE
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <structmember.h>
#include "numpy/arrayobject.h"
+#include "numpy/npy_math.h"
+
#include "arrayobject.h"
#include "npy_config.h"
@@ -23,6 +26,11 @@
#include "mem_overlap.h"
#include "array_assign.h"
#include "array_coercion.h"
+/* TODO: Only for `NpyIter_GetTransferFlags` until it is public */
+#define NPY_ITERATOR_IMPLEMENTATION_CODE
+#include "nditer_impl.h"
+
+#include "umathmodule.h"
#define HAS_INTEGER 1
@@ -914,7 +922,6 @@ array_boolean_subscript(PyArrayObject *self,
char *ret_data;
PyArray_Descr *dtype;
PyArrayObject *ret;
- int needs_api = 0;
size = count_boolean_trues(PyArray_NDIM(bmask), PyArray_DATA(bmask),
PyArray_DIMS(bmask), PyArray_STRIDES(bmask));
@@ -962,13 +969,18 @@ array_boolean_subscript(PyArrayObject *self,
/* Get a dtype transfer function */
NpyIter_GetInnerFixedStrideArray(iter, fixed_strides);
NPY_cast_info cast_info;
+ /*
+ * TODO: Ignoring cast flags, since this is only ever a copy. In
+ * principle that may not be quite right in some future?
+ */
+ NPY_ARRAYMETHOD_FLAGS cast_flags;
if (PyArray_GetDTypeTransferFunction(
IsUintAligned(self) && IsAligned(self),
fixed_strides[0], itemsize,
dtype, dtype,
0,
&cast_info,
- &needs_api) != NPY_SUCCEED) {
+ &cast_flags) != NPY_SUCCEED) {
Py_DECREF(ret);
NpyIter_Deallocate(iter);
return NULL;
@@ -1068,7 +1080,6 @@ array_assign_boolean_subscript(PyArrayObject *self,
{
npy_intp size, v_stride;
char *v_data;
- int needs_api = 0;
npy_intp bmask_size;
if (PyArray_DESCR(bmask)->type_num != NPY_BOOL) {
@@ -1164,6 +1175,7 @@ array_assign_boolean_subscript(PyArrayObject *self,
/* Get a dtype transfer function */
NpyIter_GetInnerFixedStrideArray(iter, fixed_strides);
NPY_cast_info cast_info;
+ NPY_ARRAYMETHOD_FLAGS cast_flags;
if (PyArray_GetDTypeTransferFunction(
IsUintAligned(self) && IsAligned(self) &&
IsUintAligned(v) && IsAligned(v),
@@ -1171,14 +1183,17 @@ array_assign_boolean_subscript(PyArrayObject *self,
PyArray_DESCR(v), PyArray_DESCR(self),
0,
&cast_info,
- &needs_api) != NPY_SUCCEED) {
+ &cast_flags) != NPY_SUCCEED) {
NpyIter_Deallocate(iter);
return -1;
}
- if (!needs_api) {
+ if (!(cast_flags & NPY_METH_REQUIRES_PYAPI)) {
NPY_BEGIN_THREADS_NDITER(iter);
}
+ if (!(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
+ npy_clear_floatstatus_barrier((char *)self);
+ }
npy_intp strides[2] = {v_stride, self_stride};
@@ -1209,7 +1224,7 @@ array_assign_boolean_subscript(PyArrayObject *self,
}
} while (iternext(iter));
- if (!needs_api) {
+ if (!(cast_flags & NPY_METH_REQUIRES_PYAPI)) {
NPY_END_THREADS;
}
@@ -1217,6 +1232,12 @@ array_assign_boolean_subscript(PyArrayObject *self,
if (!NpyIter_Deallocate(iter)) {
res = -1;
}
+ if (res == 0 && !(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
+ int fpes = npy_get_floatstatus_barrier((char *)self);
+ if (fpes && PyUFunc_GiveFloatingpointErrors("cast", fpes) < 0) {
+ return -1;
+ }
+ }
}
return res;
@@ -1414,6 +1435,8 @@ array_subscript(PyArrayObject *self, PyObject *op)
int index_type;
int index_num;
int i, ndim, fancy_ndim;
+ NPY_cast_info cast_info = {.func = NULL};
+
/*
* Index info array. We can have twice as many indices as dimensions
* (because of None). The + 1 is to not need to check as much.
@@ -1579,7 +1602,43 @@ array_subscript(PyArrayObject *self, PyObject *op)
goto finish;
}
- if (mapiter_get(mit) < 0) {
+ /*
+ * Alignment information (swapping is never needed, since we buffer),
+ * could also check extra_op is buffered, but it should rarely matter.
+ */
+ int is_aligned = IsUintAligned(self) && IsUintAligned(mit->extra_op);
+ /*
+ * NOTE: Getting never actually casts, so we currently do not bother to do
+ * the full checks (floating point errors) here (unlike assignment).
+ */
+ int meth_flags = NpyIter_GetTransferFlags(mit->outer);
+ if (mit->extra_op_iter) {
+ int extra_op_flags = NpyIter_GetTransferFlags(mit->extra_op_iter);
+ meth_flags = PyArrayMethod_COMBINED_FLAGS(meth_flags, extra_op_flags);
+ }
+
+ if (mit->subspace_iter != NULL) {
+ int extra_op_flags = NpyIter_GetTransferFlags(mit->subspace_iter);
+ meth_flags = PyArrayMethod_COMBINED_FLAGS(meth_flags, extra_op_flags);
+
+ NPY_ARRAYMETHOD_FLAGS transfer_flags;
+ npy_intp fixed_strides[2];
+ /*
+ * Get a dtype transfer function, since there are no
+ * buffers, this is safe.
+ */
+ NpyIter_GetInnerFixedStrideArray(mit->subspace_iter, fixed_strides);
+
+ if (PyArray_GetDTypeTransferFunction(is_aligned,
+ fixed_strides[0], fixed_strides[1],
+ PyArray_DESCR(self), PyArray_DESCR(mit->extra_op),
+ 0, &cast_info, &transfer_flags) != NPY_SUCCEED) {
+ goto finish;
+ }
+ meth_flags = PyArrayMethod_COMBINED_FLAGS(meth_flags, transfer_flags);
+ }
+
+ if (mapiter_get(mit, &cast_info, meth_flags, is_aligned) < 0) {
goto finish;
}
@@ -1614,6 +1673,7 @@ array_subscript(PyArrayObject *self, PyObject *op)
}
finish:
+ NPY_cast_info_xfree(&cast_info);
Py_XDECREF(mit);
Py_XDECREF(view);
/* Clean up indices */
@@ -1699,6 +1759,9 @@ array_assign_subscript(PyArrayObject *self, PyObject *ind, PyObject *op)
PyArrayMapIterObject *mit = NULL;
+ /* When a subspace is used, casting is done manually. */
+ NPY_cast_info cast_info = {.func = NULL};
+
if (op == NULL) {
PyErr_SetString(PyExc_ValueError,
"cannot delete array elements");
@@ -1871,7 +1934,6 @@ array_assign_subscript(PyArrayObject *self, PyObject *ind, PyObject *op)
index_num == 1 && tmp_arr) {
/* The array being indexed has one dimension and it is a fancy index */
PyArrayObject *ind = (PyArrayObject*)indices[0].object;
-
/* Check if the type is equivalent */
if (PyArray_EquivTypes(PyArray_DESCR(self),
PyArray_DESCR(tmp_arr)) &&
@@ -1935,12 +1997,50 @@ array_assign_subscript(PyArrayObject *self, PyObject *ind, PyObject *op)
}
}
- /* Can now reset the outer iterator (delayed bufalloc) */
- if (NpyIter_Reset(mit->outer, NULL) < 0) {
+ if (PyArray_MapIterCheckIndices(mit) < 0) {
goto fail;
}
- if (PyArray_MapIterCheckIndices(mit) < 0) {
+ /*
+ * Alignment information (swapping is never needed, since we buffer),
+ * could also check extra_op is buffered, but it should rarely matter.
+ */
+ int is_aligned = IsUintAligned(self) && IsUintAligned(mit->extra_op);
+ int meth_flags = NpyIter_GetTransferFlags(mit->outer);
+
+ if (mit->extra_op_iter) {
+ int extra_op_flags = NpyIter_GetTransferFlags(mit->extra_op_iter);
+ meth_flags = PyArrayMethod_COMBINED_FLAGS(meth_flags, extra_op_flags);
+ }
+
+ if (mit->subspace_iter != NULL) {
+ int extra_op_flags = NpyIter_GetTransferFlags(mit->subspace_iter);
+ meth_flags = PyArrayMethod_COMBINED_FLAGS(meth_flags, extra_op_flags);
+
+ NPY_ARRAYMETHOD_FLAGS transfer_flags;
+ npy_intp fixed_strides[2];
+
+ /*
+ * Get a dtype transfer function, since there are no
+ * buffers, this is safe.
+ */
+ NpyIter_GetInnerFixedStrideArray(mit->subspace_iter, fixed_strides);
+
+ if (PyArray_GetDTypeTransferFunction(is_aligned,
+ fixed_strides[1], fixed_strides[0],
+ PyArray_DESCR(mit->extra_op), PyArray_DESCR(self),
+ 0, &cast_info, &transfer_flags) != NPY_SUCCEED) {
+ goto fail;
+ }
+ meth_flags = PyArrayMethod_COMBINED_FLAGS(meth_flags, transfer_flags);
+ }
+
+ if (!(meth_flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
+ npy_clear_floatstatus_barrier((char *)mit);
+ }
+
+ /* Can now reset the outer iterator (delayed bufalloc) */
+ if (NpyIter_Reset(mit->outer, NULL) < 0) {
goto fail;
}
@@ -1948,11 +2048,17 @@ array_assign_subscript(PyArrayObject *self, PyObject *ind, PyObject *op)
* Could add a casting check, but apparently most assignments do
* not care about safe casting.
*/
-
- if (mapiter_set(mit) < 0) {
+ if (mapiter_set(mit, &cast_info, meth_flags, is_aligned) < 0) {
goto fail;
}
+ if (!(meth_flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
+ int fpes = npy_get_floatstatus_barrier((char *)mit);
+ if (fpes && PyUFunc_GiveFloatingpointErrors("cast", fpes) < 0) {
+ goto fail;
+ }
+ }
+
Py_DECREF(mit);
goto success;
@@ -1961,6 +2067,8 @@ array_assign_subscript(PyArrayObject *self, PyObject *ind, PyObject *op)
Py_XDECREF((PyObject *)view);
Py_XDECREF((PyObject *)tmp_arr);
Py_XDECREF((PyObject *)mit);
+ NPY_cast_info_xfree(&cast_info);
+
for (i=0; i < index_num; i++) {
Py_XDECREF(indices[i].object);
}
@@ -1969,6 +2077,8 @@ array_assign_subscript(PyArrayObject *self, PyObject *ind, PyObject *op)
success:
Py_XDECREF((PyObject *)view);
Py_XDECREF((PyObject *)tmp_arr);
+ NPY_cast_info_xfree(&cast_info);
+
for (i=0; i < index_num; i++) {
Py_XDECREF(indices[i].object);
}
@@ -2089,7 +2199,7 @@ _nonzero_indices(PyObject *myBool, PyArrayObject **arrays)
/* Reset the map iterator to the beginning */
-NPY_NO_EXPORT void
+NPY_NO_EXPORT int
PyArray_MapIterReset(PyArrayMapIterObject *mit)
{
npy_intp indval;
@@ -2097,12 +2207,16 @@ PyArray_MapIterReset(PyArrayMapIterObject *mit)
int i;
if (mit->size == 0) {
- return;
+ return 0;
}
- NpyIter_Reset(mit->outer, NULL);
+ if (!NpyIter_Reset(mit->outer, NULL)) {
+ return -1;
+ }
if (mit->extra_op_iter) {
- NpyIter_Reset(mit->extra_op_iter, NULL);
+ if (!NpyIter_Reset(mit->extra_op_iter, NULL)) {
+ return -1;
+ }
baseptrs[1] = mit->extra_op_ptrs[0];
}
@@ -2119,14 +2233,16 @@ PyArray_MapIterReset(PyArrayMapIterObject *mit)
mit->dataptr = baseptrs[0];
if (mit->subspace_iter) {
- NpyIter_ResetBasePointers(mit->subspace_iter, baseptrs, NULL);
+ if (!NpyIter_ResetBasePointers(mit->subspace_iter, baseptrs, NULL)) {
+ return -1;
+ }
mit->iter_count = *NpyIter_GetInnerLoopSizePtr(mit->subspace_iter);
}
else {
mit->iter_count = *NpyIter_GetInnerLoopSizePtr(mit->outer);
}
- return;
+ return 0;
}
@@ -2592,13 +2708,14 @@ PyArray_MapIterNew(npy_index_info *indices , int index_num, int index_type,
}
/* create new MapIter object */
- mit = (PyArrayMapIterObject *)PyArray_malloc(sizeof(PyArrayMapIterObject));
+ mit = (PyArrayMapIterObject *)PyArray_malloc(
+ sizeof(PyArrayMapIterObject) + sizeof(NPY_cast_info));
if (mit == NULL) {
Py_DECREF(intp_descr);
return NULL;
}
/* set all attributes of mapiter to zero */
- memset(mit, 0, sizeof(PyArrayMapIterObject));
+ memset(mit, 0, sizeof(PyArrayMapIterObject) + sizeof(NPY_cast_info));
PyObject_Init((PyObject *)mit, &PyArrayMapIter_Type);
Py_INCREF(arr);
@@ -2874,6 +2991,11 @@ PyArray_MapIterNew(npy_index_info *indices , int index_num, int index_type,
/* If external array is iterated, and no subspace is needed */
nops = mit->numiter;
+
+ if (!uses_subspace) {
+ outer_flags |= NPY_ITER_EXTERNAL_LOOP;
+ }
+
if (extra_op_flags && !uses_subspace) {
/*
* NOTE: This small limitation should practically not matter.
@@ -2921,9 +3043,6 @@ PyArray_MapIterNew(npy_index_info *indices , int index_num, int index_type,
if (mit->outer == NULL) {
goto fail;
}
- if (!uses_subspace) {
- NpyIter_EnableExternalLoop(mit->outer);
- }
mit->outer_next = NpyIter_GetIterNext(mit->outer, NULL);
if (mit->outer_next == NULL) {
@@ -3061,7 +3180,7 @@ PyArray_MapIterNew(npy_index_info *indices , int index_num, int index_type,
mit->subspace_ptrs = NpyIter_GetDataPtrArray(mit->subspace_iter);
mit->subspace_strides = NpyIter_GetInnerStrideArray(mit->subspace_iter);
- if (NpyIter_IterationNeedsAPI(mit->outer)) {
+ if (NpyIter_IterationNeedsAPI(mit->subspace_iter)) {
mit->needs_api = 1;
/*
* NOTE: In this case, need to call PyErr_Occurred() after
@@ -3212,9 +3331,12 @@ PyArray_MapIterArrayCopyIfOverlap(PyArrayObject * a, PyObject * index,
goto fail;
}
+ if (PyArray_MapIterReset(mit) < 0) {
+ goto fail;
+ }
+
Py_XDECREF(a_copy);
Py_XDECREF(subspace);
- PyArray_MapIterReset(mit);
for (i=0; i < index_num; i++) {
Py_XDECREF(indices[i].object);
diff --git a/numpy/core/src/multiarray/mapping.h b/numpy/core/src/multiarray/mapping.h
index e929b8b3f..4e5d06238 100644
--- a/numpy/core/src/multiarray/mapping.h
+++ b/numpy/core/src/multiarray/mapping.h
@@ -51,7 +51,7 @@ array_assign_item(PyArrayObject *self, Py_ssize_t i, PyObject *v);
* Prototypes for Mapping calls --- not part of the C-API
* because only useful as part of a getitem call.
*/
-NPY_NO_EXPORT void
+NPY_NO_EXPORT int
PyArray_MapIterReset(PyArrayMapIterObject *mit);
NPY_NO_EXPORT void
diff --git a/numpy/core/src/multiarray/nditer_api.c b/numpy/core/src/multiarray/nditer_api.c
index 860c8c1f6..b80312e06 100644
--- a/numpy/core/src/multiarray/nditer_api.c
+++ b/numpy/core/src/multiarray/nditer_api.c
@@ -857,6 +857,13 @@ NpyIter_RequiresBuffering(NpyIter *iter)
* Whether the iteration loop, and in particular the iternext()
* function, needs API access. If this is true, the GIL must
* be retained while iterating.
+ *
+ * NOTE: Internally (currently), `NpyIter_GetTransferFlags` will
+ * additionally provide information on whether floating point errors
+ * may be given during casts. The flags only require the API use
+ * necessary for buffering though. So an iterate which does not require
+ * buffering may indicate `NpyIter_IterationNeedsAPI`, but not include
+ * the flag in `NpyIter_GetTransferFlags`.
*/
NPY_NO_EXPORT npy_bool
NpyIter_IterationNeedsAPI(NpyIter *iter)
@@ -864,6 +871,21 @@ NpyIter_IterationNeedsAPI(NpyIter *iter)
return (NIT_ITFLAGS(iter)&NPY_ITFLAG_NEEDSAPI) != 0;
}
+
+/*
+ * Fetch the ArrayMethod (runtime) flags for all "transfer functions' (i.e.
+ * copy to buffer/casts).
+ *
+ * TODO: This should be public API, but that only makes sense when the
+ * ArrayMethod API is made public.
+ */
+NPY_NO_EXPORT int
+NpyIter_GetTransferFlags(NpyIter *iter)
+{
+ return NIT_ITFLAGS(iter) >> NPY_ITFLAG_TRANSFERFLAGS_SHIFT;
+}
+
+
/*NUMPY_API
* Gets the number of dimensions being iterated
*/
diff --git a/numpy/core/src/multiarray/nditer_constr.c b/numpy/core/src/multiarray/nditer_constr.c
index f82a9624e..a383c63e8 100644
--- a/numpy/core/src/multiarray/nditer_constr.c
+++ b/numpy/core/src/multiarray/nditer_constr.c
@@ -3141,7 +3141,9 @@ npyiter_allocate_transfer_functions(NpyIter *iter)
npy_intp *strides = NAD_STRIDES(axisdata), op_stride;
NpyIter_TransferInfo *transferinfo = NBF_TRANSFERINFO(bufferdata);
- int needs_api = 0;
+ /* combined cast flags, the new cast flags for each cast: */
+ NPY_ARRAYMETHOD_FLAGS cflags = PyArrayMethod_MINIMAL_FLAGS;
+ NPY_ARRAYMETHOD_FLAGS nc_flags;
for (iop = 0; iop < nop; ++iop) {
npyiter_opitflags flags = op_itflags[iop];
@@ -3167,10 +3169,11 @@ npyiter_allocate_transfer_functions(NpyIter *iter)
op_dtype[iop],
move_references,
&transferinfo[iop].read,
- &needs_api) != NPY_SUCCEED) {
+ &nc_flags) != NPY_SUCCEED) {
iop -= 1; /* This one cannot be cleaned up yet. */
goto fail;
}
+ cflags = PyArrayMethod_COMBINED_FLAGS(cflags, nc_flags);
}
else {
transferinfo[iop].read.func = NULL;
@@ -3199,9 +3202,10 @@ npyiter_allocate_transfer_functions(NpyIter *iter)
mask_dtype,
move_references,
&transferinfo[iop].write,
- &needs_api) != NPY_SUCCEED) {
+ &nc_flags) != NPY_SUCCEED) {
goto fail;
}
+ cflags = PyArrayMethod_COMBINED_FLAGS(cflags, nc_flags);
}
else {
if (PyArray_GetDTypeTransferFunction(
@@ -3212,9 +3216,10 @@ npyiter_allocate_transfer_functions(NpyIter *iter)
PyArray_DESCR(op[iop]),
move_references,
&transferinfo[iop].write,
- &needs_api) != NPY_SUCCEED) {
+ &nc_flags) != NPY_SUCCEED) {
goto fail;
}
+ cflags = PyArrayMethod_COMBINED_FLAGS(cflags, nc_flags);
}
}
/* If no write back but there are references make a decref fn */
@@ -3230,9 +3235,10 @@ npyiter_allocate_transfer_functions(NpyIter *iter)
op_dtype[iop], NULL,
1,
&transferinfo[iop].write,
- &needs_api) != NPY_SUCCEED) {
+ &nc_flags) != NPY_SUCCEED) {
goto fail;
}
+ cflags = PyArrayMethod_COMBINED_FLAGS(cflags, nc_flags);
}
else {
transferinfo[iop].write.func = NULL;
@@ -3244,8 +3250,12 @@ npyiter_allocate_transfer_functions(NpyIter *iter)
}
}
- /* If any of the dtype transfer functions needed the API, flag it */
- if (needs_api) {
+ /* Store the combined transfer flags on the iterator */
+ NIT_ITFLAGS(iter) |= cflags << NPY_ITFLAG_TRANSFERFLAGS_SHIFT;
+ assert(NIT_ITFLAGS(iter) >> NPY_ITFLAG_TRANSFERFLAGS_SHIFT == cflags);
+
+ /* If any of the dtype transfer functions needed the API, flag it. */
+ if (cflags & NPY_METH_REQUIRES_PYAPI) {
NIT_ITFLAGS(iter) |= NPY_ITFLAG_NEEDSAPI;
}
diff --git a/numpy/core/src/multiarray/nditer_impl.h b/numpy/core/src/multiarray/nditer_impl.h
index 2a82b7e54..459675ea8 100644
--- a/numpy/core/src/multiarray/nditer_impl.h
+++ b/numpy/core/src/multiarray/nditer_impl.h
@@ -76,33 +76,38 @@
/* Internal iterator flags */
/* The perm is the identity */
-#define NPY_ITFLAG_IDENTPERM 0x0001
+#define NPY_ITFLAG_IDENTPERM (1 << 0)
/* The perm has negative entries (indicating flipped axes) */
-#define NPY_ITFLAG_NEGPERM 0x0002
+#define NPY_ITFLAG_NEGPERM (1 << 1)
/* The iterator is tracking an index */
-#define NPY_ITFLAG_HASINDEX 0x0004
+#define NPY_ITFLAG_HASINDEX (1 << 2)
/* The iterator is tracking a multi-index */
-#define NPY_ITFLAG_HASMULTIINDEX 0x0008
+#define NPY_ITFLAG_HASMULTIINDEX (1 << 3)
/* The iteration order was forced on construction */
-#define NPY_ITFLAG_FORCEDORDER 0x0010
+#define NPY_ITFLAG_FORCEDORDER (1 << 4)
/* The inner loop is handled outside the iterator */
-#define NPY_ITFLAG_EXLOOP 0x0020
+#define NPY_ITFLAG_EXLOOP (1 << 5)
/* The iterator is ranged */
-#define NPY_ITFLAG_RANGE 0x0040
+#define NPY_ITFLAG_RANGE (1 << 6)
/* The iterator is buffered */
-#define NPY_ITFLAG_BUFFER 0x0080
+#define NPY_ITFLAG_BUFFER (1 << 7)
/* The iterator should grow the buffered inner loop when possible */
-#define NPY_ITFLAG_GROWINNER 0x0100
+#define NPY_ITFLAG_GROWINNER (1 << 8)
/* There is just one iteration, can specialize iternext for that */
-#define NPY_ITFLAG_ONEITERATION 0x0200
+#define NPY_ITFLAG_ONEITERATION (1 << 9)
/* Delay buffer allocation until first Reset* call */
-#define NPY_ITFLAG_DELAYBUF 0x0400
+#define NPY_ITFLAG_DELAYBUF (1 << 10)
/* Iteration needs API access during iternext */
-#define NPY_ITFLAG_NEEDSAPI 0x0800
+#define NPY_ITFLAG_NEEDSAPI (1 << 11)
/* Iteration includes one or more operands being reduced */
-#define NPY_ITFLAG_REDUCE 0x1000
+#define NPY_ITFLAG_REDUCE (1 << 12)
/* Reduce iteration doesn't need to recalculate reduce loops next time */
-#define NPY_ITFLAG_REUSE_REDUCE_LOOPS 0x2000
+#define NPY_ITFLAG_REUSE_REDUCE_LOOPS (1 << 13)
+/*
+ * Offset of (combined) ArrayMethod flags for all transfer functions.
+ * For now, we use the top 8 bits.
+ */
+#define NPY_ITFLAG_TRANSFERFLAGS_SHIFT 24
/* Internal iterator per-operand iterator flags */
@@ -356,4 +361,12 @@ npyiter_copy_to_buffers(NpyIter *iter, char **prev_dataptrs);
NPY_NO_EXPORT void
npyiter_clear_buffers(NpyIter *iter);
+/*
+ * Function to get the ArrayMethod flags of the transfer functions.
+ * TODO: This function should be public and removed from `nditer_impl.h`, but
+ * this requires making the ArrayMethod flags public API first.
+ */
+NPY_NO_EXPORT int
+NpyIter_GetTransferFlags(NpyIter *iter);
+
#endif /* NUMPY_CORE_SRC_MULTIARRAY_NDITER_IMPL_H_ */
diff --git a/numpy/core/src/umath/extobj.c b/numpy/core/src/umath/extobj.c
index 6b9a27e26..893429107 100644
--- a/numpy/core/src/umath/extobj.c
+++ b/numpy/core/src/umath/extobj.c
@@ -267,6 +267,33 @@ _extract_pyvals(PyObject *ref, const char *name, int *bufsize,
}
/*
+ * Handler which uses the default `np.errstate` given that `fpe_errors` is
+ * already set. `fpe_errors` is typically the (nonzero) result of
+ * `npy_get_floatstatus_barrier`.
+ *
+ * Returns -1 on failure (an error was raised) and 0 on success.
+ */
+NPY_NO_EXPORT int
+PyUFunc_GiveFloatingpointErrors(const char *name, int fpe_errors)
+{
+ int bufsize, errmask;
+ PyObject *errobj;
+
+ if (PyUFunc_GetPyValues((char *)name, &bufsize, &errmask,
+ &errobj) < 0) {
+ return -1;
+ }
+ int first = 1;
+ if (PyUFunc_handlefperr(errmask, errobj, fpe_errors, &first)) {
+ Py_XDECREF(errobj);
+ return -1;
+ }
+ Py_XDECREF(errobj);
+ return 0;
+}
+
+
+/*
* check the floating point status
* - errmask: mask of status to check
* - extobj: ufunc pyvals object
diff --git a/numpy/core/src/umath/ufunc_object.c b/numpy/core/src/umath/ufunc_object.c
index fce7d61de..2636396d3 100644
--- a/numpy/core/src/umath/ufunc_object.c
+++ b/numpy/core/src/umath/ufunc_object.c
@@ -57,6 +57,10 @@
#include "legacy_array_method.h"
#include "abstractdtypes.h"
+/* TODO: Only for `NpyIter_GetTransferFlags` until it is public */
+#define NPY_ITERATOR_IMPLEMENTATION_CODE
+#include "nditer_impl.h"
+
/********** PRINTF DEBUG TRACING **************/
#define NPY_UF_DBG_TRACING 0
@@ -1544,10 +1548,6 @@ execute_ufunc_loop(PyArrayMethod_Context *context, int masked,
if (masked) {
baseptrs[nop] = PyArray_BYTES(op_it[nop]);
}
- if (NpyIter_ResetBasePointers(iter, baseptrs, NULL) != NPY_SUCCEED) {
- NpyIter_Deallocate(iter);
- return -1;
- }
/*
* Get the inner loop, with the possibility of specialization
@@ -1584,17 +1584,25 @@ execute_ufunc_loop(PyArrayMethod_Context *context, int masked,
char **dataptr = NpyIter_GetDataPtrArray(iter);
npy_intp *strides = NpyIter_GetInnerStrideArray(iter);
npy_intp *countptr = NpyIter_GetInnerLoopSizePtr(iter);
- int needs_api = NpyIter_IterationNeedsAPI(iter);
NPY_BEGIN_THREADS_DEF;
+ flags = PyArrayMethod_COMBINED_FLAGS(flags, NpyIter_GetTransferFlags(iter));
+
if (!(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
npy_clear_floatstatus_barrier((char *)context);
}
- if (!needs_api && !(flags & NPY_METH_REQUIRES_PYAPI)) {
+ if (!(flags & NPY_METH_REQUIRES_PYAPI)) {
NPY_BEGIN_THREADS_THRESHOLDED(full_size);
}
+ /* The reset may copy the first buffer chunk, which could cause FPEs */
+ if (NpyIter_ResetBasePointers(iter, baseptrs, NULL) != NPY_SUCCEED) {
+ NPY_AUXDATA_FREE(auxdata);
+ NpyIter_Deallocate(iter);
+ return -1;
+ }
+
NPY_UF_DBG_PRINT("Actual inner loop:\n");
/* Execute the loop */
int res;
@@ -2388,7 +2396,8 @@ PyUFunc_GeneralizedFunctionInternal(PyUFuncObject *ufunc,
NPY_ITER_MULTI_INDEX |
NPY_ITER_REFS_OK |
NPY_ITER_ZEROSIZE_OK |
- NPY_ITER_COPY_IF_OVERLAP;
+ NPY_ITER_COPY_IF_OVERLAP |
+ NPY_ITER_DELAY_BUFALLOC;
/* Create the iterator */
iter = NpyIter_AdvancedNew(nop, op, iter_flags,
diff --git a/numpy/core/tests/test_array_coercion.py b/numpy/core/tests/test_array_coercion.py
index b8b8ef187..ed3ef7e67 100644
--- a/numpy/core/tests/test_array_coercion.py
+++ b/numpy/core/tests/test_array_coercion.py
@@ -373,28 +373,29 @@ class TestScalarDiscovery:
assert discovered_dtype.itemsize == dtype.itemsize
@pytest.mark.parametrize("dtype", np.typecodes["Integer"])
- def test_scalar_to_int_coerce_does_not_cast(self, dtype):
+ @pytest.mark.parametrize(["scalar", "error"],
+ [(np.float64(np.nan), ValueError),
+ (np.ulonglong(-1), OverflowError)])
+ def test_scalar_to_int_coerce_does_not_cast(self, dtype, scalar, error):
"""
Signed integers are currently different in that they do not cast other
NumPy scalar, but instead use scalar.__int__(). The hardcoded
exception to this rule is `np.array(scalar, dtype=integer)`.
"""
dtype = np.dtype(dtype)
- invalid_int = np.ulonglong(-1)
- float_nan = np.float64(np.nan)
-
- for scalar in [float_nan, invalid_int]:
- # This is a special case using casting logic and thus not failing:
+ # This is a special case using casting logic. It warns for the NaN
+ # but allows the cast (giving undefined behaviour).
+ with np.errstate(invalid="ignore"):
coerced = np.array(scalar, dtype=dtype)
cast = np.array(scalar).astype(dtype)
- assert_array_equal(coerced, cast)
+ assert_array_equal(coerced, cast)
- # However these fail:
- with pytest.raises((ValueError, OverflowError)):
- np.array([scalar], dtype=dtype)
- with pytest.raises((ValueError, OverflowError)):
- cast[()] = scalar
+ # However these fail:
+ with pytest.raises(error):
+ np.array([scalar], dtype=dtype)
+ with pytest.raises(error):
+ cast[()] = scalar
class TestTimeScalars:
diff --git a/numpy/core/tests/test_casting_floatingpoint_errors.py b/numpy/core/tests/test_casting_floatingpoint_errors.py
new file mode 100644
index 000000000..4fafc4ed8
--- /dev/null
+++ b/numpy/core/tests/test_casting_floatingpoint_errors.py
@@ -0,0 +1,153 @@
+import pytest
+from pytest import param
+
+import numpy as np
+
+
+def values_and_dtypes():
+ """
+ Generate value+dtype pairs that generate floating point errors during
+ casts. The invalid casts to integers will generate "invalid" value
+ warnings, the float casts all generate "overflow".
+
+ (The Python int/float paths don't need to get tested in all the same
+ situations, but it does not hurt.)
+ """
+ # Casting to float16:
+ yield param(70000, "float16", id="int-to-f2")
+ yield param("70000", "float16", id="str-to-f2")
+ yield param(70000.0, "float16", id="float-to-f2")
+ yield param(np.longdouble(70000.), "float16", id="longdouble-to-f2")
+ yield param(np.float64(70000.), "float16", id="double-to-f2")
+ yield param(np.float32(70000.), "float16", id="float-to-f2")
+ # Casting to float32:
+ yield param(10**100, "float32", id="int-to-f4")
+ yield param(1e100, "float32", id="float-to-f2")
+ yield param(np.longdouble(1e300), "float32", id="longdouble-to-f2")
+ yield param(np.float64(1e300), "float32", id="double-to-f2")
+ # Casting to float64:
+ # If longdouble is double-double, its max can be rounded down to the double
+ # max. So we correct the double spacing (a bit weird, admittedly):
+ max_ld = np.finfo(np.longdouble).max
+ spacing = np.spacing(np.nextafter(np.finfo("f8").max, 0))
+ if max_ld - spacing > np.finfo("f8").max:
+ yield param(np.finfo(np.longdouble).max, "float64",
+ id="longdouble-to-f8")
+
+ # Cast to complex32:
+ yield param(2e300, "complex64", id="float-to-c8")
+ yield param(2e300+0j, "complex64", id="complex-to-c8")
+ yield param(2e300j, "complex64", id="complex-to-c8")
+ yield param(np.longdouble(2e300), "complex64", id="longdouble-to-c8")
+
+ # Invalid float to integer casts:
+ with np.errstate(over="ignore"):
+ for to_dt in np.typecodes["AllInteger"]:
+ for value in [np.inf, np.nan]:
+ for from_dt in np.typecodes["AllFloat"]:
+ from_dt = np.dtype(from_dt)
+ from_val = from_dt.type(value)
+
+ yield param(from_val, to_dt, id=f"{from_val}-to-{to_dt}")
+
+
+def check_operations(dtype, value):
+ """
+ There are many dedicated paths in NumPy which cast and should check for
+ floating point errors which occurred during those casts.
+ """
+ if dtype.kind != 'i':
+ # These assignments use the stricter setitem logic:
+ def assignment():
+ arr = np.empty(3, dtype=dtype)
+ arr[0] = value
+
+ yield assignment
+
+ def fill():
+ arr = np.empty(3, dtype=dtype)
+ arr.fill(value)
+
+ yield fill
+
+ def copyto_scalar():
+ arr = np.empty(3, dtype=dtype)
+ np.copyto(arr, value, casting="unsafe")
+
+ yield copyto_scalar
+
+ def copyto():
+ arr = np.empty(3, dtype=dtype)
+ np.copyto(arr, np.array([value, value, value]), casting="unsafe")
+
+ yield copyto
+
+ def copyto_scalar_masked():
+ arr = np.empty(3, dtype=dtype)
+ np.copyto(arr, value, casting="unsafe",
+ where=[True, False, True])
+
+ yield copyto_scalar_masked
+
+ def copyto_masked():
+ arr = np.empty(3, dtype=dtype)
+ np.copyto(arr, np.array([value, value, value]), casting="unsafe",
+ where=[True, False, True])
+
+ yield copyto_masked
+
+ def direct_cast():
+ np.array([value, value, value]).astype(dtype)
+
+ yield direct_cast
+
+ def direct_cast_nd_strided():
+ arr = np.full((5, 5, 5), fill_value=value)[:, ::2, :]
+ arr.astype(dtype)
+
+ yield direct_cast_nd_strided
+
+ def boolean_array_assignment():
+ arr = np.empty(3, dtype=dtype)
+ arr[[True, False, True]] = np.array([value, value])
+
+ yield boolean_array_assignment
+
+ def integer_array_assignment():
+ arr = np.empty(3, dtype=dtype)
+ values = np.array([value, value])
+
+ arr[[0, 1]] = values
+
+ yield integer_array_assignment
+
+ def integer_array_assignment_with_subspace():
+ arr = np.empty((5, 3), dtype=dtype)
+ values = np.array([value, value, value])
+
+ arr[[0, 2]] = values
+
+ yield integer_array_assignment_with_subspace
+
+ def flat_assignment():
+ arr = np.empty((3,), dtype=dtype)
+ values = np.array([value, value, value])
+ arr.flat[:] = values
+
+ yield flat_assignment
+
+@pytest.mark.parametrize(["value", "dtype"], values_and_dtypes())
+@pytest.mark.filterwarnings("ignore::numpy.ComplexWarning")
+def test_floatingpoint_errors_casting(dtype, value):
+ dtype = np.dtype(dtype)
+ for operation in check_operations(dtype, value):
+ dtype = np.dtype(dtype)
+
+ match = "invalid" if dtype.kind in 'iu' else "overflow"
+ with pytest.warns(RuntimeWarning, match=match):
+ operation()
+
+ with np.errstate(all="raise"):
+ with pytest.raises(FloatingPointError, match=match):
+ operation()
+
diff --git a/numpy/core/tests/test_half.py b/numpy/core/tests/test_half.py
index 1b6fd21e1..6743dfb51 100644
--- a/numpy/core/tests/test_half.py
+++ b/numpy/core/tests/test_half.py
@@ -104,9 +104,9 @@ class TestHalf:
# Increase the float by a minimal value:
if offset == "up":
- f16s_float = np.nextafter(f16s_float, float_t(1e50))
+ f16s_float = np.nextafter(f16s_float, float_t(np.inf))
elif offset == "down":
- f16s_float = np.nextafter(f16s_float, float_t(-1e50))
+ f16s_float = np.nextafter(f16s_float, float_t(-np.inf))
# Convert back to float16 and its bit pattern:
res_patterns = f16s_float.astype(np.float16).view(np.uint16)
@@ -233,12 +233,14 @@ class TestHalf:
np.inf]
# Check float64->float16 rounding
- b = np.array(a, dtype=float16)
+ with np.errstate(over="ignore"):
+ b = np.array(a, dtype=float16)
assert_equal(b, rounded)
# Check float32->float16 rounding
a = np.array(a, dtype=float32)
- b = np.array(a, dtype=float16)
+ with np.errstate(over="ignore"):
+ b = np.array(a, dtype=float16)
assert_equal(b, rounded)
def test_half_correctness(self):
diff --git a/numpy/core/tests/test_indexing.py b/numpy/core/tests/test_indexing.py
index efcb92c2e..9ef30eae2 100644
--- a/numpy/core/tests/test_indexing.py
+++ b/numpy/core/tests/test_indexing.py
@@ -1297,11 +1297,10 @@ class TestBooleanIndexing:
def test_boolean_indexing_weirdness(self):
# Weird boolean indexing things
a = np.ones((2, 3, 4))
- a[False, True, ...].shape == (0, 2, 3, 4)
- a[True, [0, 1], True, True, [1], [[2]]] == (1, 2)
+ assert a[False, True, ...].shape == (0, 2, 3, 4)
+ assert a[True, [0, 1], True, True, [1], [[2]]].shape == (1, 2)
assert_raises(IndexError, lambda: a[False, [0, 1], ...])
-
def test_boolean_indexing_fast_path(self):
# These used to either give the wrong error, or incorrectly give no
# error.
diff --git a/numpy/core/tests/test_numeric.py b/numpy/core/tests/test_numeric.py
index 0b03c6576..5b15e29b4 100644
--- a/numpy/core/tests/test_numeric.py
+++ b/numpy/core/tests/test_numeric.py
@@ -2939,7 +2939,9 @@ class TestLikeFuncs:
self.check_like_function(np.full_like, 1, True)
self.check_like_function(np.full_like, 1000, True)
self.check_like_function(np.full_like, 123.456, True)
- self.check_like_function(np.full_like, np.inf, True)
+ # Inf to integer casts cause invalid-value errors: ignore them.
+ with np.errstate(invalid="ignore"):
+ self.check_like_function(np.full_like, np.inf, True)
@pytest.mark.parametrize('likefunc', [np.empty_like, np.full_like,
np.zeros_like, np.ones_like])
diff --git a/numpy/core/tests/test_regression.py b/numpy/core/tests/test_regression.py
index 4388023dc..4538c825d 100644
--- a/numpy/core/tests/test_regression.py
+++ b/numpy/core/tests/test_regression.py
@@ -326,20 +326,20 @@ class TestRegression:
assert_raises(ValueError, bfa)
assert_raises(ValueError, bfb)
- def test_nonarray_assignment(self):
+ @pytest.mark.parametrize("index",
+ [np.ones(10, dtype=bool), np.arange(10)],
+ ids=["boolean-arr-index", "integer-arr-index"])
+ def test_nonarray_assignment(self, index):
# See also Issue gh-2870, test for non-array assignment
# and equivalent unsafe casted array assignment
a = np.arange(10)
- b = np.ones(10, dtype=bool)
- r = np.arange(10)
- def assign(a, b, c):
- a[b] = c
+ with pytest.raises(ValueError):
+ a[index] = np.nan
- assert_raises(ValueError, assign, a, b, np.nan)
- a[b] = np.array(np.nan) # but not this.
- assert_raises(ValueError, assign, a, r, np.nan)
- a[r] = np.array(np.nan)
+ with np.errstate(invalid="warn"):
+ with pytest.warns(RuntimeWarning, match="invalid value"):
+ a[index] = np.array(np.nan) # Only warns
def test_unpickle_dtype_with_object(self):
# Implemented in r2840
diff --git a/numpy/core/tests/test_ufunc.py b/numpy/core/tests/test_ufunc.py
index 852044d32..3466178a3 100644
--- a/numpy/core/tests/test_ufunc.py
+++ b/numpy/core/tests/test_ufunc.py
@@ -620,8 +620,9 @@ class TestUfunc:
atol = max(np.finfo(dtout).tiny, 3e-308)
else:
atol = 3e-308
- # Some test values result in invalid for float16.
- with np.errstate(invalid='ignore'):
+ # Some test values result in invalid for float16
+ # and the cast to it may overflow to inf.
+ with np.errstate(invalid='ignore', over='ignore'):
res = np.true_divide(x, y, dtype=dtout)
if not np.isfinite(res) and tcout == 'e':
continue
@@ -665,20 +666,22 @@ class TestUfunc:
for dt in (int, np.float16, np.float32, np.float64, np.longdouble):
for v in (0, 1, 2, 7, 8, 9, 15, 16, 19, 127,
128, 1024, 1235):
- tgt = dt(v * (v + 1) / 2)
- d = np.arange(1, v + 1, dtype=dt)
-
# warning if sum overflows, which it does in float16
- overflow = not np.isfinite(tgt)
-
with warnings.catch_warnings(record=True) as w:
- warnings.simplefilter("always")
- assert_almost_equal(np.sum(d), tgt)
+ warnings.simplefilter("always", RuntimeWarning)
+
+ tgt = dt(v * (v + 1) / 2)
+ overflow = not np.isfinite(tgt)
assert_equal(len(w), 1 * overflow)
- assert_almost_equal(np.sum(d[::-1]), tgt)
+ d = np.arange(1, v + 1, dtype=dt)
+
+ assert_almost_equal(np.sum(d), tgt)
assert_equal(len(w), 2 * overflow)
+ assert_almost_equal(np.sum(d[::-1]), tgt)
+ assert_equal(len(w), 3 * overflow)
+
d = np.ones(500, dtype=dt)
assert_almost_equal(np.sum(d[::2]), 250.)
assert_almost_equal(np.sum(d[1::2]), 250.)
@@ -2454,7 +2457,7 @@ def test_ufunc_warn_with_nan(ufunc):
@pytest.mark.skipif(not HAS_REFCOUNT, reason="Python lacks refcounts")
-def test_ufunc_casterrors():
+def test_ufunc_out_casterrors():
# Tests that casting errors are correctly reported and buffers are
# cleared.
# The following array can be added to itself as an object array, but
@@ -2485,6 +2488,28 @@ def test_ufunc_casterrors():
assert out[-1] == 1
+@pytest.mark.parametrize("bad_offset", [0, int(np.BUFSIZE * 1.5)])
+def test_ufunc_input_casterrors(bad_offset):
+ value = 123
+ arr = np.array([value] * bad_offset +
+ ["string"] +
+ [value] * int(1.5 * np.BUFSIZE), dtype=object)
+ with pytest.raises(ValueError):
+ # Force cast inputs, but the buffered cast of `arr` to intp fails:
+ np.add(arr, arr, dtype=np.intp, casting="unsafe")
+
+
+@pytest.mark.parametrize("bad_offset", [0, int(np.BUFSIZE * 1.5)])
+def test_ufunc_input_floatingpoint_error(bad_offset):
+ value = 123
+ arr = np.array([value] * bad_offset +
+ [np.nan] +
+ [value] * int(1.5 * np.BUFSIZE))
+ with np.errstate(invalid="raise"), pytest.raises(FloatingPointError):
+ # Force cast inputs, but the buffered cast of `arr` to intp fails:
+ np.add(arr, arr, dtype=np.intp, casting="unsafe")
+
+
def test_trivial_loop_invalid_cast():
# This tests the fast-path "invalid cast", see gh-19904.
with pytest.raises(TypeError,
diff --git a/numpy/ma/tests/test_core.py b/numpy/ma/tests/test_core.py
index 679ed2090..4fac897de 100644
--- a/numpy/ma/tests/test_core.py
+++ b/numpy/ma/tests/test_core.py
@@ -4164,7 +4164,11 @@ class TestMaskedArrayFunctions:
# test that masked_where on a structured array sets a structured
# mask (see issue #2972)
a = np.zeros(10, dtype=[("A", "<f2"), ("B", "<f4")])
- am = np.ma.masked_where(a["A"] < 5, a)
+ with np.errstate(over="ignore"):
+ # NOTE: The float16 "uses" 1e20 as mask, which overflows to inf
+ # and warns. Unrelated to this test, but probably undesired.
+ # But NumPy previously did not warn for this overflow.
+ am = np.ma.masked_where(a["A"] < 5, a)
assert_equal(am.mask.dtype.names, am.dtype.names)
assert_equal(am["A"],
np.ma.masked_array(np.zeros(10), np.ones(10)))
@@ -4340,7 +4344,10 @@ class TestMaskedArrayFunctions:
tmp[(xm <= 2).filled(True)] = True
assert_equal(d._mask, tmp)
- ixm = xm.astype(int)
+ with np.errstate(invalid="warn"):
+ # The fill value is 1e20, it cannot be converted to `int`:
+ with pytest.warns(RuntimeWarning, match="invalid value"):
+ ixm = xm.astype(int)
d = where(ixm > 2, ixm, masked)
assert_equal(d, [-9, -9, -9, -9, -9, 4, -9, -9, 10, -9, -9, 3])
assert_equal(d.dtype, ixm.dtype)