From f236760314d5c32bf4d9398f1cffc3c68be5eaea Mon Sep 17 00:00:00 2001 From: Eric Moore Date: Sat, 30 Apr 2016 22:45:50 -0400 Subject: [PATCH 1/2] BUG: integer 0 to a negative power should error. Fixes gh-7510. --- numpy/core/src/umath/loops.c.src | 11 ++++++++++- numpy/core/tests/test_regression.py | 13 ------------- numpy/core/tests/test_umath.py | 9 +++++++++ 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/numpy/core/src/umath/loops.c.src b/numpy/core/src/umath/loops.c.src index a61896323167..8c2a46459b1a 100644 --- a/numpy/core/src/umath/loops.c.src +++ b/numpy/core/src/umath/loops.c.src @@ -967,7 +967,16 @@ NPY_NO_EXPORT void *((@type@ *)op1) = 1; continue; } - if (in2 < 0 || in1 == 0) { + if (in1 == 0 && in2 < 0) { + NPY_ALLOW_C_API_DEF + NPY_ALLOW_C_API; + PyErr_SetString(PyExc_ZeroDivisionError, + "0 cannot be raised to a negative power."); + NPY_DISABLE_C_API; + return; + } + + if (in2 < 0) { *((@type@ *)op1) = 0; continue; } diff --git a/numpy/core/tests/test_regression.py b/numpy/core/tests/test_regression.py index 3b167b5e24d4..bd68dde9af86 100644 --- a/numpy/core/tests/test_regression.py +++ b/numpy/core/tests/test_regression.py @@ -1242,19 +1242,6 @@ def test_array_ndmin_overflow(self): "Ticket #947." self.assertRaises(ValueError, lambda: np.array([1], ndmin=33)) - def test_errobj_reference_leak(self, level=rlevel): - # Ticket #955 - with np.errstate(all="ignore"): - z = int(0) - p = np.int32(-1) - - gc.collect() - n_before = len(gc.get_objects()) - z**p # this shouldn't leak a reference to errobj - gc.collect() - n_after = len(gc.get_objects()) - assert_(n_before >= n_after, (n_before, n_after)) - def test_void_scalar_with_titles(self, level=rlevel): # No ticket data = [('john', 4), ('mary', 5)] diff --git a/numpy/core/tests/test_umath.py b/numpy/core/tests/test_umath.py index 970bf0628bc4..7c45b1ade550 100644 --- a/numpy/core/tests/test_umath.py +++ b/numpy/core/tests/test_umath.py @@ -437,6 +437,15 @@ def test_integer_power_of_1(self): arr = np.arange(-10, 10) assert_equal(np.power(1, arr), np.ones_like(arr)) + def test_integer_zero_to_negative_power(self): + dtypes = ['i1', 'i2', 'i4', 'i8'] + for dt in dtypes: + a = np.array([0, 1, 2, 3], dtype=dt) + barray = np.array([0, 1, 2, -3], dtype=dt) + bscalar = barray[-1] + assert_raises(ZeroDivisionError, np.power, a[:, None], barray) + assert_raises(ZeroDivisionError, np.power, a, bscalar) + class TestLog2(TestCase): def test_log2_values(self): From f4f8a1c01c701e92c8b290b58682da8dfbdaac67 Mon Sep 17 00:00:00 2001 From: Charles Harris Date: Sat, 8 Oct 2016 12:21:20 -0600 Subject: [PATCH 2/2] ENH: Power ufunc raises error for integer to negative integer powers. This is a change in behaviour. Previously * zero to negative integer powers returned least integral value. * 1, -1 to negative integer powers returned correct values * all remaining integers returned zero when raised to negative integer powers. All of these cases now raise a ``ValueError``. It was felt that a simple rule was the best way to go rather than have special exceptions for the units. If you need negative powers, use an inexact type. --- doc/release/1.12.0-notes.rst | 19 +++++++++------ numpy/core/src/umath/loops.c.src | 26 +++++++++----------- numpy/core/tests/test_umath.py | 42 +++++++++++++++++++++++--------- 3 files changed, 54 insertions(+), 33 deletions(-) diff --git a/doc/release/1.12.0-notes.rst b/doc/release/1.12.0-notes.rst index 83c1f1320c8b..91233d02bd09 100644 --- a/doc/release/1.12.0-notes.rst +++ b/doc/release/1.12.0-notes.rst @@ -73,18 +73,24 @@ DeprecationWarning to error * Non-integers used as index values raise ``TypeError``, e.g., in ``reshape``, ``take``, and specifying reduce axis. +``power`` and ``**`` raise errors for integer to negative integer powers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The previous behavior was + +* zero to negative integer powers returned least integral value. +* 1, -1 to negative integer powers returned correct values +* all remaining integers returned zero when raised to negative integer powers. + +All of these cases now raise a ``ValueError``. It was felt that a simple rule +was the best way to go rather than have special exceptions for the units. If +you need negative powers, use an inexact type. + Relaxed stride checking is the default ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - This will have some impact on code that assumed that ``F_CONTIGUOUS`` and ``C_CONTIGUOUS`` were mutually exclusive and could be set to determine the default order for arrays that are now both. -``MaskedArray`` takes view of data **and** mask when slicing -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -XXX - - ``np.percentile`` 'midpoint' interpolation method fixed for exact indices ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 'midpoint' interpolator now gives the same result as 'lower' and 'higher' when @@ -93,7 +99,6 @@ the two coincide. Previous behavior of 'lower' + 0.5 is fixed. ``keepdims`` kwarg is passed through to user-class methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - numpy functions that take a ``keepdims`` kwarg now pass the value through to the corresponding methods on ndarray sub-classes. Previously the ``keepdims`` keyword would be silently dropped. These functions now have diff --git a/numpy/core/src/umath/loops.c.src b/numpy/core/src/umath/loops.c.src index 8c2a46459b1a..12dc324e8157 100644 --- a/numpy/core/src/umath/loops.c.src +++ b/numpy/core/src/umath/loops.c.src @@ -787,6 +787,7 @@ BOOL__ones_like(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UN * npy_long, npy_ulong, npy_longlong, npy_ulonglong# * #ftype = npy_float, npy_float, npy_float, npy_float, npy_double, npy_double, * npy_double, npy_double, npy_double, npy_double# + * #SIGNED = 1, 0, 1, 0, 1, 0, 1, 0, 1, 0# */ #define @TYPE@_floor_divide @TYPE@_divide @@ -959,25 +960,22 @@ NPY_NO_EXPORT void @type@ in2 = *(@type@ *)ip2; @type@ out; - if (in2 == 0) { - *((@type@ *)op1) = 1; - continue; - } - if (in1 == 1) { - *((@type@ *)op1) = 1; - continue; - } - if (in1 == 0 && in2 < 0) { +#if @SIGNED@ + if (in2 < 0) { NPY_ALLOW_C_API_DEF NPY_ALLOW_C_API; - PyErr_SetString(PyExc_ZeroDivisionError, - "0 cannot be raised to a negative power."); + PyErr_SetString(PyExc_ValueError, + "Integers to negative integer powers are not allowed."); NPY_DISABLE_C_API; return; } - - if (in2 < 0) { - *((@type@ *)op1) = 0; +#endif + if (in2 == 0) { + *((@type@ *)op1) = 1; + continue; + } + if (in1 == 1) { + *((@type@ *)op1) = 1; continue; } diff --git a/numpy/core/tests/test_umath.py b/numpy/core/tests/test_umath.py index 7c45b1ade550..4538c53acaa6 100644 --- a/numpy/core/tests/test_umath.py +++ b/numpy/core/tests/test_umath.py @@ -426,25 +426,43 @@ def test_fast_power(self): def test_integer_power(self): a = np.array([15, 15], 'i8') - b = a ** a + b = np.power(a, a) assert_equal(b, [437893890380859375, 437893890380859375]) - def test_integer_power_with_zero_exponent(self): - arr = np.arange(-10, 10) - assert_equal(np.power(arr, 0), np.ones_like(arr)) + def test_integer_power_with_integer_zero_exponent(self): + dtypes = np.typecodes['Integer'] + for dt in dtypes: + arr = np.arange(-10, 10, dtype=dt) + assert_equal(np.power(arr, 0), np.ones_like(arr)) + + dtypes = np.typecodes['UnsignedInteger'] + for dt in dtypes: + arr = np.arange(10, dtype=dt) + assert_equal(np.power(arr, 0), np.ones_like(arr)) def test_integer_power_of_1(self): - arr = np.arange(-10, 10) - assert_equal(np.power(1, arr), np.ones_like(arr)) + dtypes = np.typecodes['AllInteger'] + for dt in dtypes: + arr = np.arange(10, dtype=dt) + assert_equal(np.power(1, arr), np.ones_like(arr)) + + def test_integer_power_of_zero(self): + dtypes = np.typecodes['AllInteger'] + for dt in dtypes: + arr = np.arange(1, 10, dtype=dt) + assert_equal(np.power(0, arr), np.zeros_like(arr)) - def test_integer_zero_to_negative_power(self): - dtypes = ['i1', 'i2', 'i4', 'i8'] + def test_integer_to_negative_power(self): + dtypes = np.typecodes['Integer'] for dt in dtypes: a = np.array([0, 1, 2, 3], dtype=dt) - barray = np.array([0, 1, 2, -3], dtype=dt) - bscalar = barray[-1] - assert_raises(ZeroDivisionError, np.power, a[:, None], barray) - assert_raises(ZeroDivisionError, np.power, a, bscalar) + b = np.array([0, 1, 2, -3], dtype=dt) + one = np.array(1, dtype=dt) + minusone = np.array(-1, dtype=dt) + assert_raises(ValueError, np.power, a, b) + assert_raises(ValueError, np.power, a, minusone) + assert_raises(ValueError, np.power, one, b) + assert_raises(ValueError, np.power, one, minusone) class TestLog2(TestCase):