summaryrefslogtreecommitdiff
path: root/numpy
diff options
context:
space:
mode:
authorSebastian Berg <sebastian@sipsolutions.net>2022-04-04 11:49:19 -0700
committerSebastian Berg <sebastian@sipsolutions.net>2022-06-13 09:36:57 -0700
commit35dae708788106361a73ffd141d77f91151f483d (patch)
treef6e96b0f80f4d5b597f535e532aa3dd7d28eb3db /numpy
parent84fd4a5a0e064d1f6be7bc0c96663690faa0353b (diff)
downloadnumpy-35dae708788106361a73ffd141d77f91151f483d.tar.gz
ENH: Add overflow check to float setitem
Diffstat (limited to 'numpy')
-rw-r--r--numpy/core/src/multiarray/arraytypes.c.src51
-rw-r--r--numpy/core/tests/test_half.py4
-rw-r--r--numpy/core/tests/test_ufunc.py18
-rw-r--r--numpy/ma/tests/test_core.py6
4 files changed, 65 insertions, 14 deletions
diff --git a/numpy/core/src/multiarray/arraytypes.c.src b/numpy/core/src/multiarray/arraytypes.c.src
index 62a96e371..ca06643ce 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,
@@ -238,7 +264,6 @@ static int
}
else {
temp = (@type@)@func2@(op);
- // TODO: Need to check for inf, if input is not inf (for float)!
}
if (PyErr_Occurred()) {
PyObject *type, *value, *traceback;
@@ -364,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);
diff --git a/numpy/core/tests/test_half.py b/numpy/core/tests/test_half.py
index b60f7ba9c..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)
diff --git a/numpy/core/tests/test_ufunc.py b/numpy/core/tests/test_ufunc.py
index 1142d66f5..56ca7f4bd 100644
--- a/numpy/core/tests/test_ufunc.py
+++ b/numpy/core/tests/test_ufunc.py
@@ -666,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.)
diff --git a/numpy/ma/tests/test_core.py b/numpy/ma/tests/test_core.py
index 04540bc70..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)))