diff --git a/doc/release/upcoming_changes/21437.improvement.rst b/doc/release/upcoming_changes/21437.improvement.rst new file mode 100644 index 000000000000..256bfd600288 --- /dev/null +++ b/doc/release/upcoming_changes/21437.improvement.rst @@ -0,0 +1,33 @@ +NumPy now gives floating point errors in casts +---------------------------------------------- + +In most cases, NumPy previously did not give floating point +warnings or errors when these happened during casts. +For examples, casts like:: + + np.array([2e300]).astype(np.float32) # overflow for float32 + np.array([np.inf]).astype(np.int64) + +Should now generally give floating point warnings. These warnings +should warn that floating point overflow occurred. +For errors when converting floating point values to integers users +should expect invalid value warnings. + +Users can modify the behavior of these warnings using `np.errstate`. + +Note that for float to int casts, the exact warnings that are given may +be platform dependend. For example:: + + arr = np.full(100, value=1000, dtype=np.float64) + arr.astype(np.int8) + +May give a result equivalent to (the intermediat means no warning is given):: + + arr.astype(np.int64).astype(np.int8) + +May may return an undefined result, with a warning set:: + + RuntimeWarning: invalid value encountered in cast + +The precise behavior if subject to the C99 standard and its implementation +in both software and hardware. diff --git a/numpy/core/include/numpy/ndarraytypes.h b/numpy/core/include/numpy/ndarraytypes.h index c295f34bb51a..97e0f4e2a470 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 118ce9cb1e0b..924a34db5ee3 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 fe44fe403783..0c69f8f545b4 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 020a7f29a615..9d5bf687576d 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 #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 4ffef7ecc96e..ba964b86d3a9 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 #include +#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 0886d2d0b403..e703e73820b4 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 6e6a026bc963..c9ec8903d1bb 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 ee4f5f312bf0..a9f8dfdd2d40 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 8d0a4cd56cae..ce2c73a7a4e2 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 95a3bd84d82b..c3d66dd6bd64 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 @@ -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 18de5d1321d2..f8458d2d7b42 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 #include #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 e313d244768d..8e3afd3cc658 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 #include #include @@ -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 1a2ade11b93b..98c2d7edaa27 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 #include #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 e929b8b3f729..4e5d06238795 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 860c8c1f65fa..b80312e06770 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 f82a9624eeb0..a383c63e8d5b 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 2a82b7e5410d..459675ea8574 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 6b9a27e2621a..893429107f77 100644 --- a/numpy/core/src/umath/extobj.c +++ b/numpy/core/src/umath/extobj.c @@ -266,6 +266,33 @@ _extract_pyvals(PyObject *ref, const char *name, int *bufsize, return 0; } +/* + * 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 diff --git a/numpy/core/src/umath/ufunc_object.c b/numpy/core/src/umath/ufunc_object.c index fce7d61deb58..2636396d35cb 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 b8b8ef187668..ed3ef7e678ec 100644 --- a/numpy/core/tests/test_array_coercion.py +++ b/numpy/core/tests/test_array_coercion.py @@ -373,28 +373,29 @@ def test_default_dtype_instance(self, dtype_char): 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 000000000000..4fafc4ed8091 --- /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 1b6fd21e14bb..6743dfb51208 100644 --- a/numpy/core/tests/test_half.py +++ b/numpy/core/tests/test_half.py @@ -104,9 +104,9 @@ def test_half_conversion_rounding(self, float_t, shift, offset): # 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 @@ def test_half_rounding(self): 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 efcb92c2e6d1..9ef30eae2652 100644 --- a/numpy/core/tests/test_indexing.py +++ b/numpy/core/tests/test_indexing.py @@ -1297,11 +1297,10 @@ def test_bool_as_int_argument_errors(self): 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 0b03c65760f4..5b15e29b46ce 100644 --- a/numpy/core/tests/test_numeric.py +++ b/numpy/core/tests/test_numeric.py @@ -2939,7 +2939,9 @@ def test_filled_like(self): 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 4388023dc1d9..4538c825db64 100644 --- a/numpy/core/tests/test_regression.py +++ b/numpy/core/tests/test_regression.py @@ -326,20 +326,20 @@ def bfb(): 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 852044d32fcc..3466178a33dc 100644 --- a/numpy/core/tests/test_ufunc.py +++ b/numpy/core/tests/test_ufunc.py @@ -620,8 +620,9 @@ def test_true_divide(self): 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 @@ def test_sum(self): 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 679ed2090859..4fac897de314 100644 --- a/numpy/ma/tests/test_core.py +++ b/numpy/ma/tests/test_core.py @@ -4164,7 +4164,11 @@ def test_masked_where_structured(self): # test that masked_where on a structured array sets a structured # mask (see issue #2972) a = np.zeros(10, dtype=[("A", " 2, ixm, masked) assert_equal(d, [-9, -9, -9, -9, -9, 4, -9, -9, 10, -9, -9, 3]) assert_equal(d.dtype, ixm.dtype)