diff options
Diffstat (limited to 'numpy')
-rw-r--r-- | numpy/core/code_generators/generate_umath.py | 7 | ||||
-rw-r--r-- | numpy/core/code_generators/ufunc_docstrings.py | 50 | ||||
-rw-r--r-- | numpy/core/src/multiarray/number.c | 39 | ||||
-rw-r--r-- | numpy/core/src/multiarray/number.h | 1 | ||||
-rw-r--r-- | numpy/core/src/umath/loops.c.src | 66 | ||||
-rw-r--r-- | numpy/core/src/umath/loops.h.src | 6 | ||||
-rw-r--r-- | numpy/core/tests/test_half.py | 1 | ||||
-rw-r--r-- | numpy/core/tests/test_multiarray.py | 63 | ||||
-rw-r--r-- | numpy/core/tests/test_scalarmath.py | 102 | ||||
-rw-r--r-- | numpy/core/tests/test_umath.py | 85 | ||||
-rw-r--r-- | numpy/lib/arraysetops.py | 1 | ||||
-rw-r--r-- | numpy/lib/mixins.py | 8 | ||||
-rw-r--r-- | numpy/lib/tests/test_mixins.py | 92 |
13 files changed, 332 insertions, 189 deletions
diff --git a/numpy/core/code_generators/generate_umath.py b/numpy/core/code_generators/generate_umath.py index 45476f931..2241618f7 100644 --- a/numpy/core/code_generators/generate_umath.py +++ b/numpy/core/code_generators/generate_umath.py @@ -786,6 +786,13 @@ defdict = { TD(intflt), TD(O, f='PyNumber_Remainder'), ), +'divmod': + Ufunc(2, 2, None, + docstrings.get('numpy.core.umath.divmod'), + None, + TD(intflt), + TD(O, f='PyNumber_Divmod'), + ), 'hypot': Ufunc(2, 1, Zero, docstrings.get('numpy.core.umath.hypot'), diff --git a/numpy/core/code_generators/ufunc_docstrings.py b/numpy/core/code_generators/ufunc_docstrings.py index c783d4595..11f956b81 100644 --- a/numpy/core/code_generators/ufunc_docstrings.py +++ b/numpy/core/code_generators/ufunc_docstrings.py @@ -1290,6 +1290,7 @@ add_newdoc('numpy.core.umath', 'floor_divide', See Also -------- remainder : Remainder complementary to floor_divide. + divmod : Simultaneous floor division and remainder. divide : Standard division. floor : Round a number to the nearest integer toward minus infinity. ceil : Round a number to the nearest integer toward infinity. @@ -2509,6 +2510,11 @@ add_newdoc('numpy.core.umath', 'modf', ----- For integer input the return values are floats. + See Also + -------- + divmod : ``divmod(x, 1)`` is equivalent to ``modf`` with the return values + switched, except it always has a positive remainder. + Examples -------- >>> np.modf([0, 3.5]) @@ -2576,6 +2582,8 @@ add_newdoc('numpy.core.umath', 'positive', """ Numerical positive, element-wise. + .. versionadded:: 1.13.0 + Parameters ---------- x : array_like or scalar @@ -2878,6 +2886,7 @@ add_newdoc('numpy.core.umath', 'remainder', See Also -------- floor_divide : Equivalent of Python ``//`` operator. + divmod : Simultaneous floor division and remainder. fmod : Equivalent of the Matlab(TM) ``rem`` function. divide, floor @@ -2895,6 +2904,47 @@ add_newdoc('numpy.core.umath', 'remainder', """) +add_newdoc('numpy.core.umath', 'divmod', + """ + Return element-wise quotient and remainder simultaneously. + + .. versionadded:: 1.13.0 + + ``np.divmod(x, y)`` is equivalent to ``(x // y, x % y)``, but faster + because it avoids redundant work. It is used to implement the Python + built-in function ``divmod`` on NumPy arrays. + + Parameters + ---------- + x1 : array_like + Dividend array. + x2 : array_like + Divisor array. + out : tuple of ndarray, optional + Arrays into which the output is placed. Their types are preserved and + must be of the right shape to hold the output. + + Returns + ------- + out1 : ndarray + Element-wise quotient resulting from floor division. + out2 : ndarray + Element-wise remainder from floor division. + + See Also + -------- + floor_divide : Equivalent to Python's ``//`` operator. + remainder : Equivalent to Python's ``%`` operator. + modf : Equivalent to ``divmod(x, 1)`` for positive ``x`` with the return + values switched. + + Examples + -------- + >>> np.divmod(np.arange(5), 3) + (array([0, 0, 0, 1, 1]), array([0, 1, 2, 0, 1])) + + """) + add_newdoc('numpy.core.umath', 'right_shift', """ Shift the bits of an integer to the right. diff --git a/numpy/core/src/multiarray/number.c b/numpy/core/src/multiarray/number.c index d6598cdb6..b8239c972 100644 --- a/numpy/core/src/multiarray/number.c +++ b/numpy/core/src/multiarray/number.c @@ -83,6 +83,7 @@ PyArray_SetNumericOps(PyObject *dict) SET(multiply); SET(divide); SET(remainder); + SET(divmod); SET(power); SET(square); SET(reciprocal); @@ -135,6 +136,7 @@ PyArray_GetNumericOps(void) GET(multiply); GET(divide); GET(remainder); + GET(divmod); GET(power); GET(square); GET(reciprocal); @@ -344,6 +346,12 @@ array_remainder(PyArrayObject *m1, PyObject *m2) return PyArray_GenericBinaryFunction(m1, m2, n_ops.remainder); } +static PyObject * +array_divmod(PyArrayObject *m1, PyObject *m2) +{ + BINOP_GIVE_UP_IF_NEEDED(m1, m2, nb_divmod, array_divmod); + return PyArray_GenericBinaryFunction(m1, m2, n_ops.divmod); +} #if PY_VERSION_HEX >= 0x03050000 /* Need this to be version dependent on account of the slot check */ @@ -796,37 +804,6 @@ _array_nonzero(PyArrayObject *mp) } - -static PyObject * -array_divmod(PyArrayObject *op1, PyObject *op2) -{ - PyObject *divp, *modp, *result; - - BINOP_GIVE_UP_IF_NEEDED(op1, op2, nb_divmod, array_divmod); - - divp = array_floor_divide(op1, op2); - if (divp == NULL) { - return NULL; - } - else if(divp == Py_NotImplemented) { - return divp; - } - modp = array_remainder(op1, op2); - if (modp == NULL) { - Py_DECREF(divp); - return NULL; - } - else if(modp == Py_NotImplemented) { - Py_DECREF(divp); - return modp; - } - result = Py_BuildValue("OO", divp, modp); - Py_DECREF(divp); - Py_DECREF(modp); - return result; -} - - NPY_NO_EXPORT PyObject * array_int(PyArrayObject *v) { diff --git a/numpy/core/src/multiarray/number.h b/numpy/core/src/multiarray/number.h index 86f681c10..113fc2475 100644 --- a/numpy/core/src/multiarray/number.h +++ b/numpy/core/src/multiarray/number.h @@ -7,6 +7,7 @@ typedef struct { PyObject *multiply; PyObject *divide; PyObject *remainder; + PyObject *divmod; PyObject *power; PyObject *square; PyObject *reciprocal; diff --git a/numpy/core/src/umath/loops.c.src b/numpy/core/src/umath/loops.c.src index e88b87b5c..40ebc119a 100644 --- a/numpy/core/src/umath/loops.c.src +++ b/numpy/core/src/umath/loops.c.src @@ -1114,6 +1114,34 @@ NPY_NO_EXPORT void } } +NPY_NO_EXPORT void +@TYPE@_divmod(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) +{ + BINARY_LOOP_TWO_OUT { + const @type@ in1 = *(@type@ *)ip1; + const @type@ in2 = *(@type@ *)ip2; + /* see FIXME note for divide above */ + if (in2 == 0 || (in1 == NPY_MIN_@TYPE@ && in2 == -1)) { + npy_set_floatstatus_divbyzero(); + *((@type@ *)op1) = 0; + *((@type@ *)op2) = 0; + } + else { + /* handle mixed case the way Python does */ + const @type@ quo = in1 / in2; + const @type@ rem = in1 % in2; + if ((in1 > 0) == (in2 > 0) || rem == 0) { + *((@type@ *)op1) = quo; + *((@type@ *)op2) = rem; + } + else { + *((@type@ *)op1) = quo - 1; + *((@type@ *)op2) = rem + in2; + } + } + } +} + /**end repeat**/ /**begin repeat @@ -1168,6 +1196,24 @@ NPY_NO_EXPORT void } } +NPY_NO_EXPORT void +@TYPE@_divmod(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) +{ + BINARY_LOOP_TWO_OUT { + const @type@ in1 = *(@type@ *)ip1; + const @type@ in2 = *(@type@ *)ip2; + if (in2 == 0) { + npy_set_floatstatus_divbyzero(); + *((@type@ *)op1) = 0; + *((@type@ *)op2) = 0; + } + else { + *((@type@ *)op1)= in1/in2; + *((@type@ *)op2) = in1 % in2; + } + } +} + /**end repeat**/ /* @@ -1841,6 +1887,16 @@ NPY_NO_EXPORT void } NPY_NO_EXPORT void +@TYPE@_divmod(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) +{ + BINARY_LOOP_TWO_OUT { + const @type@ in1 = *(@type@ *)ip1; + const @type@ in2 = *(@type@ *)ip2; + *((@type@ *)op1) = npy_divmod@c@(in1, in2, (@type@ *)op2); + } +} + +NPY_NO_EXPORT void @TYPE@_square(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(data)) { char * margs[] = {args[0], args[0], args[1]}; @@ -2169,6 +2225,16 @@ HALF_remainder(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNU } NPY_NO_EXPORT void +HALF_divmod(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) +{ + BINARY_LOOP_TWO_OUT { + const npy_half in1 = *(npy_half *)ip1; + const npy_half in2 = *(npy_half *)ip2; + *((npy_half *)op1) = npy_half_divmod(in1, in2, (npy_half *)op2); + } +} + +NPY_NO_EXPORT void HALF_square(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(data)) { UNARY_LOOP { diff --git a/numpy/core/src/umath/loops.h.src b/numpy/core/src/umath/loops.h.src index c1b451c5b..4243c6522 100644 --- a/numpy/core/src/umath/loops.h.src +++ b/numpy/core/src/umath/loops.h.src @@ -140,6 +140,9 @@ NPY_NO_EXPORT void NPY_NO_EXPORT void @S@@TYPE@_remainder(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); +NPY_NO_EXPORT void +@S@@TYPE@_divmod(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); + /**end repeat1**/ /**end repeat**/ @@ -220,6 +223,9 @@ NPY_NO_EXPORT void @TYPE@_remainder(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); NPY_NO_EXPORT void +@TYPE@_divmod(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); + +NPY_NO_EXPORT void @TYPE@_square(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(data)); NPY_NO_EXPORT void diff --git a/numpy/core/tests/test_half.py b/numpy/core/tests/test_half.py index 75b74f407..7a4d36333 100644 --- a/numpy/core/tests/test_half.py +++ b/numpy/core/tests/test_half.py @@ -317,6 +317,7 @@ class TestHalf(TestCase): assert_equal(np.floor_divide(a, b), [0, 0, 2, 1, 0]) assert_equal(np.remainder(a, b), [0, 1, 0, 0, 2]) + assert_equal(np.divmod(a, b), ([0, 0, 2, 1, 0], [0, 1, 0, 0, 2])) assert_equal(np.square(b), [4, 25, 1, 16, 9]) assert_equal(np.reciprocal(b), [-0.5, 0.199951171875, 1, 0.25, 0.333251953125]) assert_equal(np.ones_like(b), [1, 1, 1, 1, 1]) diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index 8ab8c0ca7..97d801731 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -2881,7 +2881,7 @@ class TestBinop(object): 'truediv': (np.true_divide, True, float), 'floordiv': (np.floor_divide, True, float), 'mod': (np.remainder, True, float), - 'divmod': (None, False, float), + 'divmod': (np.divmod, False, float), 'pow': (np.power, True, int), 'lshift': (np.left_shift, True, int), 'rshift': (np.right_shift, True, int), @@ -2944,13 +2944,15 @@ class TestBinop(object): def check(obj, binop_override_expected, ufunc_override_expected, inplace_override_expected, check_scalar=True): for op, (ufunc, has_inplace, dtype) in ops.items(): + err_msg = ('op: %s, ufunc: %s, has_inplace: %s, dtype: %s' + % (op, ufunc, has_inplace, dtype)) check_objs = [np.arange(3, 5, dtype=dtype)] if check_scalar: check_objs.append(check_objs[0][0]) for arr in check_objs: arr_method = getattr(arr, "__{0}__".format(op)) - def norm(result): + def first_out_arg(result): if op == "divmod": assert_(isinstance(result, tuple)) return result[0] @@ -2959,77 +2961,78 @@ class TestBinop(object): # arr __op__ obj if binop_override_expected: - assert_equal(arr_method(obj), NotImplemented) + assert_equal(arr_method(obj), NotImplemented, err_msg) elif ufunc_override_expected: - assert_equal(norm(arr_method(obj))[0], - "__array_ufunc__") + assert_equal(arr_method(obj)[0], "__array_ufunc__", + err_msg) else: if (isinstance(obj, np.ndarray) and (type(obj).__array_ufunc__ is np.ndarray.__array_ufunc__)): # __array__ gets ignored - res = norm(arr_method(obj)) - assert_(res.__class__ is obj.__class__) + res = first_out_arg(arr_method(obj)) + assert_(res.__class__ is obj.__class__, err_msg) else: assert_raises((TypeError, Coerced), - arr_method, obj) + arr_method, obj, err_msg=err_msg) # obj __op__ arr arr_rmethod = getattr(arr, "__r{0}__".format(op)) if ufunc_override_expected: - res = norm(arr_rmethod(obj)) - assert_equal(res[0], "__array_ufunc__") - if ufunc is not None: - assert_equal(res[1], ufunc) + res = arr_rmethod(obj) + assert_equal(res[0], "__array_ufunc__", + err_msg=err_msg) + assert_equal(res[1], ufunc, err_msg=err_msg) else: if (isinstance(obj, np.ndarray) and (type(obj).__array_ufunc__ is np.ndarray.__array_ufunc__)): # __array__ gets ignored - res = norm(arr_rmethod(obj)) - assert_(res.__class__ is obj.__class__) + res = first_out_arg(arr_rmethod(obj)) + assert_(res.__class__ is obj.__class__, err_msg) else: # __array_ufunc__ = "asdf" creates a TypeError assert_raises((TypeError, Coerced), - arr_rmethod, obj) + arr_rmethod, obj, err_msg=err_msg) # arr __iop__ obj # array scalars don't have in-place operators if has_inplace and isinstance(arr, np.ndarray): arr_imethod = getattr(arr, "__i{0}__".format(op)) if inplace_override_expected: - assert_equal(arr_method(obj), NotImplemented) + assert_equal(arr_method(obj), NotImplemented, + err_msg=err_msg) elif ufunc_override_expected: res = arr_imethod(obj) - assert_equal(res[0], "__array_ufunc__") - if ufunc is not None: - assert_equal(res[1], ufunc) - assert_(type(res[-1]["out"]) is tuple) - assert_(res[-1]["out"][0] is arr) + assert_equal(res[0], "__array_ufunc__", err_msg) + assert_equal(res[1], ufunc, err_msg) + assert_(type(res[-1]["out"]) is tuple, err_msg) + assert_(res[-1]["out"][0] is arr, err_msg) else: if (isinstance(obj, np.ndarray) and (type(obj).__array_ufunc__ is np.ndarray.__array_ufunc__)): # __array__ gets ignored - assert_(arr_imethod(obj) is arr) + assert_(arr_imethod(obj) is arr, err_msg) else: assert_raises((TypeError, Coerced), - arr_imethod, obj) + arr_imethod, obj, + err_msg=err_msg) op_fn = getattr(operator, op, None) if op_fn is None: op_fn = getattr(operator, op + "_", None) if op_fn is None: op_fn = getattr(builtins, op) - assert_equal(op_fn(obj, arr), "forward") + assert_equal(op_fn(obj, arr), "forward", err_msg) if not isinstance(obj, np.ndarray): if binop_override_expected: - assert_equal(op_fn(arr, obj), "reverse") + assert_equal(op_fn(arr, obj), "reverse", err_msg) elif ufunc_override_expected: - assert_equal(norm(op_fn(arr, obj))[0], - "__array_ufunc__") - if ufunc_override_expected and ufunc is not None: - assert_equal(norm(ufunc(obj, arr))[0], - "__array_ufunc__") + assert_equal(op_fn(arr, obj)[0], "__array_ufunc__", + err_msg) + if ufunc_override_expected: + assert_equal(ufunc(obj, arr)[0], "__array_ufunc__", + err_msg) # No array priority, no array_ufunc -> nothing called check(make_obj(object), False, False, False) diff --git a/numpy/core/tests/test_scalarmath.py b/numpy/core/tests/test_scalarmath.py index 1cafde5a0..c76db98f8 100644 --- a/numpy/core/tests/test_scalarmath.py +++ b/numpy/core/tests/test_scalarmath.py @@ -189,30 +189,34 @@ class TestPower(TestCase): assert_raises(TypeError, operator.pow, np.array(t(a)), b, c) -class TestModulus(TestCase): +def floordiv_and_mod(x, y): + return (x // y, x % y) + + +def _signs(dt): + if dt in np.typecodes['UnsignedInteger']: + return (+1,) + else: + return (+1, -1) - floordiv = operator.floordiv - mod = operator.mod + +class TestModulus(TestCase): def test_modulus_basic(self): dt = np.typecodes['AllInteger'] + np.typecodes['Float'] - for dt1, dt2 in itertools.product(dt, dt): - for sg1, sg2 in itertools.product((+1, -1), (+1, -1)): - if sg1 == -1 and dt1 in np.typecodes['UnsignedInteger']: - continue - if sg2 == -1 and dt2 in np.typecodes['UnsignedInteger']: - continue - fmt = 'dt1: %s, dt2: %s, sg1: %s, sg2: %s' - msg = fmt % (dt1, dt2, sg1, sg2) - a = np.array(sg1*71, dtype=dt1)[()] - b = np.array(sg2*19, dtype=dt2)[()] - div = self.floordiv(a, b) - rem = self.mod(a, b) - assert_equal(div*b + rem, a, err_msg=msg) - if sg2 == -1: - assert_(b < rem <= 0, msg) - else: - assert_(b > rem >= 0, msg) + for op in [floordiv_and_mod, divmod]: + for dt1, dt2 in itertools.product(dt, dt): + for sg1, sg2 in itertools.product(_signs(dt1), _signs(dt2)): + fmt = 'op: %s, dt1: %s, dt2: %s, sg1: %s, sg2: %s' + msg = fmt % (op.__name__, dt1, dt2, sg1, sg2) + a = np.array(sg1*71, dtype=dt1)[()] + b = np.array(sg2*19, dtype=dt2)[()] + div, rem = op(a, b) + assert_equal(div*b + rem, a, err_msg=msg) + if sg2 == -1: + assert_(b < rem <= 0, msg) + else: + assert_(b > rem >= 0, msg) def test_float_modulus_exact(self): # test that float results are exact for small integers. This also @@ -231,42 +235,42 @@ class TestModulus(TestCase): tgtdiv = np.where((tgtdiv == 0.0) & ((b < 0) ^ (a < 0)), -0.0, tgtdiv) tgtrem = np.where((tgtrem == 0.0) & (b < 0), -0.0, tgtrem) - for dt in np.typecodes['Float']: - msg = 'dtype: %s' % (dt,) - fa = a.astype(dt) - fb = b.astype(dt) - # use list comprehension so a_ and b_ are scalars - div = [self.floordiv(a_, b_) for a_, b_ in zip(fa, fb)] - rem = [self.mod(a_, b_) for a_, b_ in zip(fa, fb)] - assert_equal(div, tgtdiv, err_msg=msg) - assert_equal(rem, tgtrem, err_msg=msg) + for op in [floordiv_and_mod, divmod]: + for dt in np.typecodes['Float']: + msg = 'op: %s, dtype: %s' % (op.__name__, dt) + fa = a.astype(dt) + fb = b.astype(dt) + # use list comprehension so a_ and b_ are scalars + div, rem = zip(*[op(a_, b_) for a_, b_ in zip(fa, fb)]) + assert_equal(div, tgtdiv, err_msg=msg) + assert_equal(rem, tgtrem, err_msg=msg) def test_float_modulus_roundoff(self): # gh-6127 dt = np.typecodes['Float'] - for dt1, dt2 in itertools.product(dt, dt): - for sg1, sg2 in itertools.product((+1, -1), (+1, -1)): - fmt = 'dt1: %s, dt2: %s, sg1: %s, sg2: %s' - msg = fmt % (dt1, dt2, sg1, sg2) - a = np.array(sg1*78*6e-8, dtype=dt1)[()] - b = np.array(sg2*6e-8, dtype=dt2)[()] - div = self.floordiv(a, b) - rem = self.mod(a, b) - # Equal assertion should hold when fmod is used - assert_equal(div*b + rem, a, err_msg=msg) - if sg2 == -1: - assert_(b < rem <= 0, msg) - else: - assert_(b > rem >= 0, msg) + for op in [floordiv_and_mod, divmod]: + for dt1, dt2 in itertools.product(dt, dt): + for sg1, sg2 in itertools.product((+1, -1), (+1, -1)): + fmt = 'op: %s, dt1: %s, dt2: %s, sg1: %s, sg2: %s' + msg = fmt % (op.__name__, dt1, dt2, sg1, sg2) + a = np.array(sg1*78*6e-8, dtype=dt1)[()] + b = np.array(sg2*6e-8, dtype=dt2)[()] + div, rem = op(a, b) + # Equal assertion should hold when fmod is used + assert_equal(div*b + rem, a, err_msg=msg) + if sg2 == -1: + assert_(b < rem <= 0, msg) + else: + assert_(b > rem >= 0, msg) def test_float_modulus_corner_cases(self): # Check remainder magnitude. for dt in np.typecodes['Float']: b = np.array(1.0, dtype=dt) a = np.nextafter(np.array(0.0, dtype=dt), -b) - rem = self.mod(a, b) + rem = operator.mod(a, b) assert_(rem <= b, 'dt: %s' % dt) - rem = self.mod(-a, -b) + rem = operator.mod(-a, -b) assert_(rem >= -b, 'dt: %s' % dt) # Check nans, inf @@ -277,14 +281,14 @@ class TestModulus(TestCase): fzer = np.array(0.0, dtype=dt) finf = np.array(np.inf, dtype=dt) fnan = np.array(np.nan, dtype=dt) - rem = self.mod(fone, fzer) + rem = operator.mod(fone, fzer) assert_(np.isnan(rem), 'dt: %s' % dt) # MSVC 2008 returns NaN here, so disable the check. - #rem = self.mod(fone, finf) + #rem = operator.mod(fone, finf) #assert_(rem == fone, 'dt: %s' % dt) - rem = self.mod(fone, fnan) + rem = operator.mod(fone, fnan) assert_(np.isnan(rem), 'dt: %s' % dt) - rem = self.mod(finf, fone) + rem = operator.mod(finf, fone) assert_(np.isnan(rem), 'dt: %s' % dt) diff --git a/numpy/core/tests/test_umath.py b/numpy/core/tests/test_umath.py index f13c056c3..51bf7c942 100644 --- a/numpy/core/tests/test_umath.py +++ b/numpy/core/tests/test_umath.py @@ -264,27 +264,34 @@ class TestDivision(TestCase): assert_equal(y, [1.e+110, 0], err_msg=msg) +def floor_divide_and_remainder(x, y): + return (np.floor_divide(x, y), np.remainder(x, y)) + + +def _signs(dt): + if dt in np.typecodes['UnsignedInteger']: + return (+1,) + else: + return (+1, -1) + + class TestRemainder(TestCase): def test_remainder_basic(self): dt = np.typecodes['AllInteger'] + np.typecodes['Float'] - for dt1, dt2 in itertools.product(dt, dt): - for sg1, sg2 in itertools.product((+1, -1), (+1, -1)): - if sg1 == -1 and dt1 in np.typecodes['UnsignedInteger']: - continue - if sg2 == -1 and dt2 in np.typecodes['UnsignedInteger']: - continue - fmt = 'dt1: %s, dt2: %s, sg1: %s, sg2: %s' - msg = fmt % (dt1, dt2, sg1, sg2) - a = np.array(sg1*71, dtype=dt1) - b = np.array(sg2*19, dtype=dt2) - div = np.floor_divide(a, b) - rem = np.remainder(a, b) - assert_equal(div*b + rem, a, err_msg=msg) - if sg2 == -1: - assert_(b < rem <= 0, msg) - else: - assert_(b > rem >= 0, msg) + for op in [floor_divide_and_remainder, np.divmod]: + for dt1, dt2 in itertools.product(dt, dt): + for sg1, sg2 in itertools.product(_signs(dt1), _signs(dt2)): + fmt = 'op: %s, dt1: %s, dt2: %s, sg1: %s, sg2: %s' + msg = fmt % (op.__name__, dt1, dt2, sg1, sg2) + a = np.array(sg1*71, dtype=dt1) + b = np.array(sg2*19, dtype=dt2) + div, rem = op(a, b) + assert_equal(div*b + rem, a, err_msg=msg) + if sg2 == -1: + assert_(b < rem <= 0, msg) + else: + assert_(b > rem >= 0, msg) def test_float_remainder_exact(self): # test that float results are exact for small integers. This also @@ -303,32 +310,32 @@ class TestRemainder(TestCase): tgtdiv = np.where((tgtdiv == 0.0) & ((b < 0) ^ (a < 0)), -0.0, tgtdiv) tgtrem = np.where((tgtrem == 0.0) & (b < 0), -0.0, tgtrem) - for dt in np.typecodes['Float']: - msg = 'dtype: %s' % (dt,) - fa = a.astype(dt) - fb = b.astype(dt) - div = np.floor_divide(fa, fb) - rem = np.remainder(fa, fb) - assert_equal(div, tgtdiv, err_msg=msg) - assert_equal(rem, tgtrem, err_msg=msg) + for op in [floor_divide_and_remainder, np.divmod]: + for dt in np.typecodes['Float']: + msg = 'op: %s, dtype: %s' % (op.__name__, dt) + fa = a.astype(dt) + fb = b.astype(dt) + div, rem = op(fa, fb) + assert_equal(div, tgtdiv, err_msg=msg) + assert_equal(rem, tgtrem, err_msg=msg) def test_float_remainder_roundoff(self): # gh-6127 dt = np.typecodes['Float'] - for dt1, dt2 in itertools.product(dt, dt): - for sg1, sg2 in itertools.product((+1, -1), (+1, -1)): - fmt = 'dt1: %s, dt2: %s, sg1: %s, sg2: %s' - msg = fmt % (dt1, dt2, sg1, sg2) - a = np.array(sg1*78*6e-8, dtype=dt1) - b = np.array(sg2*6e-8, dtype=dt2) - div = np.floor_divide(a, b) - rem = np.remainder(a, b) - # Equal assertion should hold when fmod is used - assert_equal(div*b + rem, a, err_msg=msg) - if sg2 == -1: - assert_(b < rem <= 0, msg) - else: - assert_(b > rem >= 0, msg) + for op in [floor_divide_and_remainder, np.divmod]: + for dt1, dt2 in itertools.product(dt, dt): + for sg1, sg2 in itertools.product((+1, -1), (+1, -1)): + fmt = 'op: %s, dt1: %s, dt2: %s, sg1: %s, sg2: %s' + msg = fmt % (op.__name__, dt1, dt2, sg1, sg2) + a = np.array(sg1*78*6e-8, dtype=dt1) + b = np.array(sg2*6e-8, dtype=dt2) + div, rem = op(a, b) + # Equal assertion should hold when fmod is used + assert_equal(div*b + rem, a, err_msg=msg) + if sg2 == -1: + assert_(b < rem <= 0, msg) + else: + assert_(b > rem >= 0, msg) def test_float_remainder_corner_cases(self): # Check remainder magnitude. diff --git a/numpy/lib/arraysetops.py b/numpy/lib/arraysetops.py index 9a1448991..d29e555b8 100644 --- a/numpy/lib/arraysetops.py +++ b/numpy/lib/arraysetops.py @@ -522,6 +522,7 @@ def isin(element, test_elements, assume_unique=False, invert=False): in1d : Flattened version of this function. numpy.lib.arraysetops : Module with a number of other functions for performing set operations on arrays. + Notes ----- diff --git a/numpy/lib/mixins.py b/numpy/lib/mixins.py index bbeed1437..fbdc2edfb 100644 --- a/numpy/lib/mixins.py +++ b/numpy/lib/mixins.py @@ -70,8 +70,7 @@ class NDArrayOperatorsMixin(object): implement. This class does not yet implement the special operators corresponding - to ``divmod`` or ``matmul`` (``@``), because these operation do not yet - have corresponding NumPy ufuncs. + to ``matmul`` (``@``), because ``np.matmul`` is not yet a NumPy ufunc. It is useful for writing classes that do not inherit from `numpy.ndarray`, but that should support arithmetic and numpy universal functions like @@ -161,7 +160,10 @@ class NDArrayOperatorsMixin(object): um.true_divide, 'truediv') __floordiv__, __rfloordiv__, __ifloordiv__ = _numeric_methods( um.floor_divide, 'floordiv') - __mod__, __rmod__, __imod__ = _numeric_methods(um.mod, 'mod') + __mod__, __rmod__, __imod__ = _numeric_methods(um.remainder, 'mod') + __divmod__ = _binary_method(um.divmod, 'divmod') + __rdivmod__ = _reflected_binary_method(um.divmod, 'divmod') + # __idivmod__ does not exist # TODO: handle the optional third argument for __pow__? __pow__, __rpow__, __ipow__ = _numeric_methods(um.power, 'pow') __lshift__, __rlshift__, __ilshift__ = _numeric_methods( diff --git a/numpy/lib/tests/test_mixins.py b/numpy/lib/tests/test_mixins.py index 287d4ed29..db38bdfd6 100644 --- a/numpy/lib/tests/test_mixins.py +++ b/numpy/lib/tests/test_mixins.py @@ -56,11 +56,47 @@ class ArrayLike(np.lib.mixins.NDArrayOperatorsMixin): return '%s(%r)' % (type(self).__name__, self.value) +def wrap_array_like(result): + if type(result) is tuple: + return tuple(ArrayLike(r) for r in result) + else: + return ArrayLike(result) + + def _assert_equal_type_and_value(result, expected, err_msg=None): assert_equal(type(result), type(expected), err_msg=err_msg) - assert_equal(result.value, expected.value, err_msg=err_msg) - assert_equal(getattr(result.value, 'dtype', None), - getattr(expected.value, 'dtype', None), err_msg=err_msg) + if isinstance(result, tuple): + assert_equal(len(result), len(expected), err_msg=err_msg) + for result_item, expected_item in zip(result, expected): + _assert_equal_type_and_value(result_item, expected_item, err_msg) + else: + assert_equal(result.value, expected.value, err_msg=err_msg) + assert_equal(getattr(result.value, 'dtype', None), + getattr(expected.value, 'dtype', None), err_msg=err_msg) + + +_ALL_BINARY_OPERATORS = [ + operator.lt, + operator.le, + operator.eq, + operator.ne, + operator.gt, + operator.ge, + operator.add, + operator.sub, + operator.mul, + operator.truediv, + operator.floordiv, + # TODO: test div on Python 2, only + operator.mod, + divmod, + pow, + operator.lshift, + operator.rshift, + operator.and_, + operator.xor, + operator.or_, +] class TestNDArrayOperatorsMixin(TestCase): @@ -148,52 +184,34 @@ class TestNDArrayOperatorsMixin(TestCase): operator.invert]: _assert_equal_type_and_value(op(array_like), ArrayLike(op(array))) - def test_binary_methods(self): + def test_forward_binary_methods(self): array = np.array([-1, 0, 1, 2]) array_like = ArrayLike(array) - operators = [ - operator.lt, - operator.le, - operator.eq, - operator.ne, - operator.gt, - operator.ge, - operator.add, - operator.sub, - operator.mul, - operator.truediv, - operator.floordiv, - # TODO: test div on Python 2, only - operator.mod, - # divmod is not yet implemented - pow, - operator.lshift, - operator.rshift, - operator.and_, - operator.xor, - operator.or_, - ] - for op in operators: - expected = ArrayLike(op(array, 1)) + for op in _ALL_BINARY_OPERATORS: + expected = wrap_array_like(op(array, 1)) actual = op(array_like, 1) err_msg = 'failed for operator {}'.format(op) _assert_equal_type_and_value(expected, actual, err_msg=err_msg) + def test_reflected_binary_methods(self): + for op in _ALL_BINARY_OPERATORS: + expected = wrap_array_like(op(2, 1)) + actual = op(2, ArrayLike(1)) + err_msg = 'failed for operator {}'.format(op) + _assert_equal_type_and_value(expected, actual, err_msg=err_msg) + def test_ufunc_at(self): array = ArrayLike(np.array([1, 2, 3, 4])) assert_(np.negative.at(array, np.array([0, 1])) is None) _assert_equal_type_and_value(array, ArrayLike([-1, -2, 3, 4])) def test_ufunc_two_outputs(self): - def check(result): - assert_(type(result) is tuple) - assert_equal(len(result), 2) - mantissa, exponent = np.frexp(2 ** -3) - _assert_equal_type_and_value(result[0], ArrayLike(mantissa)) - _assert_equal_type_and_value(result[1], ArrayLike(exponent)) - - check(np.frexp(ArrayLike(2 ** -3))) - check(np.frexp(ArrayLike(np.array(2 ** -3)))) + mantissa, exponent = np.frexp(2 ** -3) + expected = (ArrayLike(mantissa), ArrayLike(exponent)) + _assert_equal_type_and_value( + np.frexp(ArrayLike(2 ** -3)), expected) + _assert_equal_type_and_value( + np.frexp(ArrayLike(np.array(2 ** -3))), expected) if __name__ == "__main__": |