summaryrefslogtreecommitdiff
path: root/numpy
diff options
context:
space:
mode:
authorCharles Harris <charlesr.harris@gmail.com>2020-11-25 10:31:27 -0700
committerGitHub <noreply@github.com>2020-11-25 10:31:27 -0700
commit24a470494ebf452931ae9b7bf94689dfcca1fff0 (patch)
treeb0d4f74cc64ce20c60840fa3ed58c59975df2344 /numpy
parentba77419b101535e8ec14192c35b924b32b6a31bc (diff)
parentf527a570b7ec9b7c26e3a79601ca19cc989de244 (diff)
downloadnumpy-24a470494ebf452931ae9b7bf94689dfcca1fff0.tar.gz
Merge pull request #16161 from anirudh2290/ufunc_divide_error
BUG: Potential fix for divmod(1.0, 0.0) to raise divbyzero and return inf
Diffstat (limited to 'numpy')
-rw-r--r--numpy/core/src/npymath/npy_math_internal.h.src102
-rw-r--r--numpy/core/src/umath/loops.c.src20
-rw-r--r--numpy/core/src/umath/scalarmath.c.src12
-rw-r--r--numpy/core/tests/test_scalarmath.py7
-rw-r--r--numpy/core/tests/test_umath.py142
5 files changed, 266 insertions, 17 deletions
diff --git a/numpy/core/src/npymath/npy_math_internal.h.src b/numpy/core/src/npymath/npy_math_internal.h.src
index 18b6d1434..ff4663dc3 100644
--- a/numpy/core/src/npymath/npy_math_internal.h.src
+++ b/numpy/core/src/npymath/npy_math_internal.h.src
@@ -398,8 +398,8 @@ NPY_INPLACE @type@ npy_@kind@@c@(@type@ x)
/**end repeat1**/
/**begin repeat1
- * #kind = atan2,hypot,pow,fmod,copysign#
- * #KIND = ATAN2,HYPOT,POW,FMOD,COPYSIGN#
+ * #kind = atan2,hypot,pow,copysign#
+ * #KIND = ATAN2,HYPOT,POW,COPYSIGN#
*/
#ifdef @kind@@c@
#undef @kind@@c@
@@ -412,6 +412,32 @@ NPY_INPLACE @type@ npy_@kind@@c@(@type@ x, @type@ y)
#endif
/**end repeat1**/
+/**begin repeat1
+ * #kind = fmod#
+ * #KIND = FMOD#
+ */
+#ifdef @kind@@c@
+#undef @kind@@c@
+#endif
+#ifndef HAVE_MODF@C@
+NPY_INPLACE @type@
+npy_@kind@@c@(@type@ x, @type@ y)
+{
+ int are_inputs_inf = (npy_isinf(x) && npy_isinf(y));
+ /* force set invalid flag, doesnt raise by default on gcc < 8 */
+ if (npy_isnan(x) || npy_isnan(y)) {
+ npy_set_floatstatus_invalid();
+ }
+ if (are_inputs_inf || !y) {
+ if (!npy_isnan(x)) {
+ npy_set_floatstatus_invalid();
+ }
+ }
+ return (@type@) npy_@kind@((double)x, (double) y);
+}
+#endif
+/**end repeat1**/
+
#ifdef modf@c@
#undef modf@c@
#endif
@@ -473,8 +499,8 @@ NPY_INPLACE @type@ npy_@kind@@c@(@type@ x)
/**end repeat1**/
/**begin repeat1
- * #kind = atan2,hypot,pow,fmod,copysign#
- * #KIND = ATAN2,HYPOT,POW,FMOD,COPYSIGN#
+ * #kind = atan2,hypot,pow,copysign#
+ * #KIND = ATAN2,HYPOT,POW,COPYSIGN#
*/
#ifdef HAVE_@KIND@@C@
NPY_INPLACE @type@ npy_@kind@@c@(@type@ x, @type@ y)
@@ -484,6 +510,29 @@ NPY_INPLACE @type@ npy_@kind@@c@(@type@ x, @type@ y)
#endif
/**end repeat1**/
+/**begin repeat1
+ * #kind = fmod#
+ * #KIND = FMOD#
+ */
+#ifdef HAVE_FMOD@C@
+NPY_INPLACE @type@
+npy_@kind@@c@(@type@ x, @type@ y)
+{
+ int are_inputs_inf = (npy_isinf(x) && npy_isinf(y));
+ /* force set invalid flag, doesnt raise by default on gcc < 8 */
+ if (npy_isnan(x) || npy_isnan(y)) {
+ npy_set_floatstatus_invalid();
+ }
+ if (are_inputs_inf || !y) {
+ if (!npy_isnan(x)) {
+ npy_set_floatstatus_invalid();
+ }
+ }
+ return @kind@@c@(x, y);
+}
+#endif
+/**end repeat1**/
+
#ifdef HAVE_MODF@C@
NPY_INPLACE @type@ npy_modf@c@(@type@ x, @type@ *iptr)
{
@@ -625,6 +674,38 @@ NPY_INPLACE @type@ npy_logaddexp2@c@(@type@ x, @type@ y)
}
/*
+ * Wrapper function for remainder edge cases
+ * Internally calls npy_divmod*
+ */
+NPY_INPLACE @type@
+npy_remainder@c@(@type@ a, @type@ b)
+{
+ @type@ mod;
+ if (NPY_UNLIKELY(!b)) {
+ mod = npy_fmod@c@(a, b);
+ } else {
+ npy_divmod@c@(a, b, &mod);
+ }
+ return mod;
+}
+
+NPY_INPLACE @type@
+npy_floor_divide@c@(@type@ a, @type@ b) {
+ @type@ div, mod;
+ if (NPY_UNLIKELY(!b)) {
+ div = a / b;
+ if (!a || npy_isnan(a)) {
+ npy_set_floatstatus_invalid();
+ } else {
+ npy_set_floatstatus_divbyzero();
+ }
+ } else {
+ div = npy_divmod@c@(a, b, &mod);
+ }
+ return div;
+}
+
+/*
* Python version of divmod.
*
* The implementation is mostly copied from cpython 3.5.
@@ -634,12 +715,19 @@ npy_divmod@c@(@type@ a, @type@ b, @type@ *modulus)
{
@type@ div, mod, floordiv;
+ /* force set invalid flag, doesnt raise by default on gcc < 8 */
+ if (npy_isnan(a) || npy_isnan(b)) {
+ npy_set_floatstatus_invalid();
+ }
mod = npy_fmod@c@(a, b);
-
- if (!b) {
+ if (NPY_UNLIKELY(!b)) {
+ div = a / b;
+ if (a && !npy_isnan(a)) {
+ npy_set_floatstatus_divbyzero();
+ }
/* If b == 0, return result of fmod. For IEEE is nan */
*modulus = mod;
- return mod;
+ return div;
}
/* a - mod should be very nearly an integer multiple of b */
diff --git a/numpy/core/src/umath/loops.c.src b/numpy/core/src/umath/loops.c.src
index c9efdeb4e..c2e06a4fd 100644
--- a/numpy/core/src/umath/loops.c.src
+++ b/numpy/core/src/umath/loops.c.src
@@ -1955,8 +1955,7 @@ NPY_NO_EXPORT void
BINARY_LOOP {
const @type@ in1 = *(@type@ *)ip1;
const @type@ in2 = *(@type@ *)ip2;
- @type@ mod;
- *((@type@ *)op1) = npy_divmod@c@(in1, in2, &mod);
+ *((@type@ *)op1) = npy_floor_divide@c@(in1, in2);
}
}
@@ -1966,7 +1965,7 @@ NPY_NO_EXPORT void
BINARY_LOOP {
const @type@ in1 = *(@type@ *)ip1;
const @type@ in2 = *(@type@ *)ip2;
- npy_divmod@c@(in1, in2, (@type@ *)op1);
+ *((@type@ *) op1) = npy_remainder@c@(in1, in2);
}
}
@@ -2306,8 +2305,13 @@ HALF_floor_divide(char **args, npy_intp const *dimensions, npy_intp const *steps
BINARY_LOOP {
const npy_half in1 = *(npy_half *)ip1;
const npy_half in2 = *(npy_half *)ip2;
- npy_half mod;
- *((npy_half *)op1) = npy_half_divmod(in1, in2, &mod);
+
+ float fh1 = npy_half_to_float(in1);
+ float fh2 = npy_half_to_float(in2);
+ float div;
+
+ div = npy_floor_dividef(fh1, fh2);
+ *((npy_half *)op1) = npy_float_to_half(div);
}
}
@@ -2317,7 +2321,11 @@ HALF_remainder(char **args, npy_intp const *dimensions, npy_intp const *steps, v
BINARY_LOOP {
const npy_half in1 = *(npy_half *)ip1;
const npy_half in2 = *(npy_half *)ip2;
- npy_half_divmod(in1, in2, (npy_half *)op1);
+ float fh1 = npy_half_to_float(in1);
+ float fh2 = npy_half_to_float(in2);
+ float mod;
+ mod = npy_remainderf(fh1, fh2);
+ *((npy_half *)op1) = npy_float_to_half(mod);
}
}
diff --git a/numpy/core/src/umath/scalarmath.c.src b/numpy/core/src/umath/scalarmath.c.src
index 55bc958cb..86dade0f1 100644
--- a/numpy/core/src/umath/scalarmath.c.src
+++ b/numpy/core/src/umath/scalarmath.c.src
@@ -285,7 +285,11 @@ static void
@name@_ctype_floor_divide(@type@ a, @type@ b, @type@ *out) {
@type@ mod;
- *out = npy_divmod@c@(a, b, &mod);
+ if (!b) {
+ *out = a / b;
+ } else {
+ *out = npy_divmod@c@(a, b, &mod);
+ }
}
@@ -318,7 +322,11 @@ static void
half_ctype_floor_divide(npy_half a, npy_half b, npy_half *out) {
npy_half mod;
- *out = npy_half_divmod(a, b, &mod);
+ if (!b) {
+ *out = a / b;
+ } else {
+ *out = npy_half_divmod(a, b, &mod);
+ }
}
diff --git a/numpy/core/tests/test_scalarmath.py b/numpy/core/tests/test_scalarmath.py
index c7f44cf50..d8529418e 100644
--- a/numpy/core/tests/test_scalarmath.py
+++ b/numpy/core/tests/test_scalarmath.py
@@ -276,6 +276,10 @@ class TestModulus:
# Check nans, inf
with suppress_warnings() as sup:
sup.filter(RuntimeWarning, "invalid value encountered in remainder")
+ sup.filter(RuntimeWarning, "divide by zero encountered in remainder")
+ sup.filter(RuntimeWarning, "divide by zero encountered in floor_divide")
+ sup.filter(RuntimeWarning, "divide by zero encountered in divmod")
+ sup.filter(RuntimeWarning, "invalid value encountered in divmod")
for dt in np.typecodes['Float']:
fone = np.array(1.0, dtype=dt)
fzer = np.array(0.0, dtype=dt)
@@ -290,6 +294,9 @@ class TestModulus:
assert_(np.isnan(rem), 'dt: %s' % dt)
rem = operator.mod(finf, fone)
assert_(np.isnan(rem), 'dt: %s' % dt)
+ for op in [floordiv_and_mod, divmod]:
+ div, mod = op(fone, fzer)
+ assert_(np.isinf(div)) and assert_(np.isnan(mod))
def test_inplace_floordiv_handling(self):
# issue gh-12927
diff --git a/numpy/core/tests/test_umath.py b/numpy/core/tests/test_umath.py
index f57493e9c..3f89cc59b 100644
--- a/numpy/core/tests/test_umath.py
+++ b/numpy/core/tests/test_umath.py
@@ -13,7 +13,7 @@ from numpy.testing import (
assert_, assert_equal, assert_raises, assert_raises_regex,
assert_array_equal, assert_almost_equal, assert_array_almost_equal,
assert_array_max_ulp, assert_allclose, assert_no_warnings, suppress_warnings,
- _gen_alignment_data, assert_array_almost_equal_nulp
+ _gen_alignment_data, assert_array_almost_equal_nulp, assert_warns
)
def on_powerpc():
@@ -293,6 +293,42 @@ class TestDivision:
assert_equal(np.signbit(x//1), 0)
assert_equal(np.signbit((-x)//1), 1)
+ @pytest.mark.parametrize('dtype', np.typecodes['Float'])
+ def test_floor_division_errors(self, dtype):
+ fnan = np.array(np.nan, dtype=dtype)
+ fone = np.array(1.0, dtype=dtype)
+ fzer = np.array(0.0, dtype=dtype)
+ finf = np.array(np.inf, dtype=dtype)
+ # divide by zero error check
+ with np.errstate(divide='raise', invalid='ignore'):
+ assert_raises(FloatingPointError, np.floor_divide, fone, fzer)
+ with np.errstate(invalid='raise'):
+ assert_raises(FloatingPointError, np.floor_divide, fnan, fone)
+ assert_raises(FloatingPointError, np.floor_divide, fone, fnan)
+ assert_raises(FloatingPointError, np.floor_divide, fnan, fzer)
+
+ @pytest.mark.parametrize('dtype', np.typecodes['Float'])
+ def test_floor_division_corner_cases(self, dtype):
+ # test corner cases like 1.0//0.0 for errors and return vals
+ x = np.zeros(10, dtype=dtype)
+ y = np.ones(10, dtype=dtype)
+ fnan = np.array(np.nan, dtype=dtype)
+ fone = np.array(1.0, dtype=dtype)
+ fzer = np.array(0.0, dtype=dtype)
+ finf = np.array(np.inf, dtype=dtype)
+ with suppress_warnings() as sup:
+ sup.filter(RuntimeWarning, "invalid value encountered in floor_divide")
+ div = np.floor_divide(fnan, fone)
+ assert(np.isnan(div)), "dt: %s, div: %s" % (dt, div)
+ div = np.floor_divide(fone, fnan)
+ assert(np.isnan(div)), "dt: %s, div: %s" % (dt, div)
+ div = np.floor_divide(fnan, fzer)
+ assert(np.isnan(div)), "dt: %s, div: %s" % (dt, div)
+ # verify 1.0//0.0 computations return inf
+ with np.errstate(divide='ignore'):
+ z = np.floor_divide(y, x)
+ assert_(np.isinf(z).all())
+
def floor_divide_and_remainder(x, y):
return (np.floor_divide(x, y), np.remainder(x, y))
@@ -366,9 +402,90 @@ class TestRemainder:
else:
assert_(b > rem >= 0, msg)
+ @pytest.mark.parametrize('dtype', np.typecodes['Float'])
+ def test_float_divmod_errors(self, dtype):
+ # Check valid errors raised for divmod and remainder
+ fzero = np.array(0.0, dtype=dtype)
+ fone = np.array(1.0, dtype=dtype)
+ finf = np.array(np.inf, dtype=dtype)
+ fnan = np.array(np.nan, dtype=dtype)
+ # since divmod is combination of both remainder and divide
+ # ops it will set both dividebyzero and invalid flags
+ with np.errstate(divide='raise', invalid='ignore'):
+ assert_raises(FloatingPointError, np.divmod, fone, fzero)
+ with np.errstate(divide='ignore', invalid='raise'):
+ assert_raises(FloatingPointError, np.divmod, fone, fzero)
+ with np.errstate(invalid='raise'):
+ assert_raises(FloatingPointError, np.divmod, fzero, fzero)
+ with np.errstate(invalid='raise'):
+ assert_raises(FloatingPointError, np.divmod, finf, finf)
+ with np.errstate(divide='ignore', invalid='raise'):
+ assert_raises(FloatingPointError, np.divmod, finf, fzero)
+ with np.errstate(divide='raise', invalid='ignore'):
+ assert_raises(FloatingPointError, np.divmod, finf, fzero)
+
+ @pytest.mark.parametrize('dtype', np.typecodes['Float'])
+ @pytest.mark.parametrize('fn', [np.fmod, np.remainder])
+ def test_float_remainder_errors(self, dtype, fn):
+ fzero = np.array(0.0, dtype=dtype)
+ fone = np.array(1.0, dtype=dtype)
+ finf = np.array(np.inf, dtype=dtype)
+ fnan = np.array(np.nan, dtype=dtype)
+ with np.errstate(invalid='raise'):
+ assert_raises(FloatingPointError, fn, fone, fzero)
+ assert_raises(FloatingPointError, fn, fnan, fzero)
+ assert_raises(FloatingPointError, fn, fone, fnan)
+ assert_raises(FloatingPointError, fn, fnan, fone)
+
+ def test_float_remainder_overflow(self):
+ a = np.finfo(np.float64).tiny
+ with np.errstate(over='ignore', invalid='ignore'):
+ div, mod = np.divmod(4, a)
+ np.isinf(div)
+ assert_(mod == 0)
+ with np.errstate(over='raise', invalid='ignore'):
+ assert_raises(FloatingPointError, np.divmod, 4, a)
+ with np.errstate(invalid='raise', over='ignore'):
+ assert_raises(FloatingPointError, np.divmod, 4, a)
+
+ def test_float_divmod_corner_cases(self):
+ # check nan cases
+ for dt in np.typecodes['Float']:
+ fnan = np.array(np.nan, dtype=dt)
+ fone = np.array(1.0, dtype=dt)
+ fzer = np.array(0.0, dtype=dt)
+ finf = np.array(np.inf, dtype=dt)
+ with suppress_warnings() as sup:
+ sup.filter(RuntimeWarning, "invalid value encountered in divmod")
+ sup.filter(RuntimeWarning, "divide by zero encountered in divmod")
+ div, rem = np.divmod(fone, fzer)
+ assert(np.isinf(div)), 'dt: %s, div: %s' % (dt, rem)
+ assert(np.isnan(rem)), 'dt: %s, rem: %s' % (dt, rem)
+ div, rem = np.divmod(fzer, fzer)
+ assert(np.isnan(rem)), 'dt: %s, rem: %s' % (dt, rem)
+ assert_(np.isnan(div)), 'dt: %s, rem: %s' % (dt, rem)
+ div, rem = np.divmod(finf, finf)
+ assert(np.isnan(div)), 'dt: %s, rem: %s' % (dt, rem)
+ assert(np.isnan(rem)), 'dt: %s, rem: %s' % (dt, rem)
+ div, rem = np.divmod(finf, fzer)
+ assert(np.isinf(div)), 'dt: %s, rem: %s' % (dt, rem)
+ assert(np.isnan(rem)), 'dt: %s, rem: %s' % (dt, rem)
+ div, rem = np.divmod(fnan, fone)
+ assert(np.isnan(rem)), "dt: %s, rem: %s" % (dt, rem)
+ assert(np.isnan(div)), "dt: %s, rem: %s" % (dt, rem)
+ div, rem = np.divmod(fone, fnan)
+ assert(np.isnan(rem)), "dt: %s, rem: %s" % (dt, rem)
+ assert(np.isnan(div)), "dt: %s, rem: %s" % (dt, rem)
+ div, rem = np.divmod(fnan, fzer)
+ assert(np.isnan(rem)), "dt: %s, rem: %s" % (dt, rem)
+ assert(np.isnan(div)), "dt: %s, rem: %s" % (dt, rem)
+
def test_float_remainder_corner_cases(self):
# Check remainder magnitude.
for dt in np.typecodes['Float']:
+ fone = np.array(1.0, dtype=dt)
+ fzer = np.array(0.0, dtype=dt)
+ fnan = np.array(np.nan, dtype=dt)
b = np.array(1.0, dtype=dt)
a = np.nextafter(np.array(0.0, dtype=dt), -b)
rem = np.remainder(a, b)
@@ -379,6 +496,7 @@ class TestRemainder:
# Check nans, inf
with suppress_warnings() as sup:
sup.filter(RuntimeWarning, "invalid value encountered in remainder")
+ sup.filter(RuntimeWarning, "invalid value encountered in fmod")
for dt in np.typecodes['Float']:
fone = np.array(1.0, dtype=dt)
fzer = np.array(0.0, dtype=dt)
@@ -389,10 +507,30 @@ class TestRemainder:
# MSVC 2008 returns NaN here, so disable the check.
#rem = np.remainder(fone, finf)
#assert_(rem == fone, 'dt: %s, rem: %s' % (dt, rem))
+ rem = np.remainder(finf, fone)
+ fmod = np.fmod(finf, fone)
+ assert_(np.isnan(fmod), 'dt: %s, fmod: %s' % (dt, fmod))
+ assert_(np.isnan(rem), 'dt: %s, rem: %s' % (dt, rem))
+ rem = np.remainder(finf, finf)
+ fmod = np.fmod(finf, fone)
+ assert_(np.isnan(rem), 'dt: %s, rem: %s' % (dt, rem))
+ assert_(np.isnan(fmod), 'dt: %s, fmod: %s' % (dt, fmod))
+ rem = np.remainder(finf, fzer)
+ fmod = np.fmod(finf, fzer)
+ assert_(np.isnan(rem), 'dt: %s, rem: %s' % (dt, rem))
+ assert_(np.isnan(fmod), 'dt: %s, fmod: %s' % (dt, fmod))
rem = np.remainder(fone, fnan)
+ fmod = np.fmod(fone, fnan)
assert_(np.isnan(rem), 'dt: %s, rem: %s' % (dt, rem))
- rem = np.remainder(finf, fone)
+ assert_(np.isnan(fmod), 'dt: %s, fmod: %s' % (dt, fmod))
+ rem = np.remainder(fnan, fzer)
+ fmod = np.fmod(fnan, fzer)
+ assert_(np.isnan(rem), 'dt: %s, rem: %s' % (dt, rem))
+ assert_(np.isnan(fmod), 'dt: %s, fmod: %s' % (dt, rem))
+ rem = np.remainder(fnan, fone)
+ fmod = np.fmod(fnan, fone)
assert_(np.isnan(rem), 'dt: %s, rem: %s' % (dt, rem))
+ assert_(np.isnan(fmod), 'dt: %s, fmod: %s' % (dt, rem))
class TestCbrt: