Skip to content

ENH: add np.divmod ufunc #9063

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 8, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions doc/neps/ufunc-overrides.rst
Original file line number Diff line number Diff line change
Expand Up @@ -662,15 +662,15 @@ Symbol Operator NumPy Ufunc(s)
``/`` ``div`` :func:`divide`
(Python 2)
``//`` ``floordiv`` :func:`floor_divide`
``%`` ``mod`` :func:`mod`
``%`` ``mod`` :func:`remainder`
NA ``divmod`` :func:`divmod`
``**`` ``pow`` :func:`power`
``<<`` ``lshift`` :func:`left_shift`
``>>`` ``rshift`` :func:`right_shift`
``&`` ``and_`` :func:`bitwise_and`
``^`` ``xor_`` :func:`bitwise_xor`
``|`` ``or_`` :func:`bitwise_or`
NA ``divmod`` :func:`floor_divide`, :func:`mod` [10]_
``@`` ``matmul`` Not yet implemented as a ufunc
``@`` ``matmul`` Not yet implemented as a ufunc [10]_
====== ============ =========================================

And here is the list of unary operators:
Expand All @@ -684,7 +684,10 @@ NA ``abs`` :func:`absolute`
``~`` ``invert`` :func:`invert`
====== ============ =========================================

.. [10] In the future, NumPy may switch to use a single ufunc ``divmod_`` instead.
.. [10] Because NumPy's :func:`matmul` is not a ufunc, it is
`currently not possible <https://github.com/numpy/numpy/issues/9028>`_
to override ``numpy_array @ other`` with ``other`` taking precedence
if ``other`` implements ``__array_func__``.
.. [11] :class:`ndarray` currently does a copy instead of using this ufunc.

Future extensions to other functions
Expand Down
7 changes: 7 additions & 0 deletions doc/release/1.13.0-notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,13 @@ New ``positive`` ufunc
This ufunc corresponds to unary `+`, but unlike `+` on an ndarray it will raise
an error if array values do not support numeric operations.

New ``divmod`` ufunc
--------------------
This ufunc corresponds to the Python builtin `divmod`, and is used to implement
`divmod` when called on numpy arrays. ``np.divmod(x, y)`` calculates a result
equivalent to ``(np.floor_divide(x, y), np.remainder(x, y))`` but is
approximately twice as fast as calling the functions separately.
Copy link
Member

@eric-wieser eric-wieser May 6, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably worth pointing out that the builtin divmod now dispatches to this - I'm an idiot, you've already done this

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And before release I'm going to gather all the new ufuncs into a New ufuncs section. There are enough of them in the 1.13 release to justify that.


Better ``repr`` of object arrays
--------------------------------
Object arrays that contain themselves no longer cause a recursion error.
Expand Down
1 change: 1 addition & 0 deletions doc/source/reference/routines.math.rst
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ Arithmetic operations
mod
modf
remainder
divmod

Handling complex numbers
------------------------
Expand Down
1 change: 1 addition & 0 deletions doc/source/reference/ufuncs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,7 @@ Math operations
remainder
mod
fmod
divmod
absolute
fabs
rint
Expand Down
7 changes: 7 additions & 0 deletions numpy/core/code_generators/generate_umath.py
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,13 @@ def english_upper(s):
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'),
Expand Down
50 changes: 50 additions & 0 deletions numpy/core/code_generators/ufunc_docstrings.py
Original file line number Diff line number Diff line change
Expand Up @@ -1290,6 +1290,7 @@ def add_newdoc(place, name, doc):
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.
Expand Down Expand Up @@ -2474,6 +2475,11 @@ def add_newdoc(place, name, doc):
-----
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])
Expand Down Expand Up @@ -2541,6 +2547,8 @@ def add_newdoc(place, name, doc):
"""
Numerical positive, element-wise.

.. versionadded:: 1.13.0

Parameters
----------
x : array_like or scalar
Expand Down Expand Up @@ -2843,6 +2851,7 @@ def add_newdoc(place, name, doc):
See Also
--------
floor_divide : Equivalent of Python ``//`` operator.
divmod : Simultaneous floor division and remainder.
fmod : Equivalent of the Matlab(TM) ``rem`` function.
divide, floor

Expand All @@ -2860,6 +2869,47 @@ def add_newdoc(place, name, doc):

""")

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: resulting should probably be in both places or neither

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The quotient is the result of floor division, but the remainder is a by-product. So I think the current language makes sense (but I'm open to alternatives if you have a concrete suggestion).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not convinced results and byproducts are disjoint sets, but I'm happy to leave this as is based on your rationale.

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.
Expand Down
39 changes: 8 additions & 31 deletions numpy/core/src/multiarray/number.c
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ PyArray_SetNumericOps(PyObject *dict)
SET(multiply);
SET(divide);
SET(remainder);
SET(divmod);
SET(power);
SET(square);
SET(reciprocal);
Expand Down Expand Up @@ -135,6 +136,7 @@ PyArray_GetNumericOps(void)
GET(multiply);
GET(divide);
GET(remainder);
GET(divmod);
GET(power);
GET(square);
GET(reciprocal);
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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)
{
Expand Down
1 change: 1 addition & 0 deletions numpy/core/src/multiarray/number.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ typedef struct {
PyObject *multiply;
PyObject *divide;
PyObject *remainder;
PyObject *divmod;
PyObject *power;
PyObject *square;
PyObject *reciprocal;
Expand Down
66 changes: 66 additions & 0 deletions numpy/core/src/umath/loops.c.src
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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**/

/*
Expand Down Expand Up @@ -1831,6 +1877,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))
{
Expand Down Expand Up @@ -2159,6 +2215,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))
{
Expand Down
6 changes: 6 additions & 0 deletions numpy/core/src/umath/loops.h.src
Original file line number Diff line number Diff line change
Expand Up @@ -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**/
Expand Down Expand Up @@ -219,6 +222,9 @@ NPY_NO_EXPORT void
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));

Expand Down
1 change: 1 addition & 0 deletions numpy/core/tests/test_half.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ def test_half_ufuncs(self):

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])
Expand Down
Loading