8000 ENH: add np.divmod ufunc by shoyer · Pull Request #9063 · numpy/numpy · GitHub
[go: up one dir, main page]

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 1 commit
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
Next Next commit
ENH: add np.divmod ufunc
  • Loading branch information
shoyer committed May 8, 2017
commit c9d1f9e467155cec3030b0970816abe928244b9c
6 changes: 6 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,12 @@ 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`. ``np.divmod(x, y)``
calculates a result equivalent to
``(np.floor_divide(x, y), np.remainder(x, y))`` but faster.

Better ``repr`` of object arrays
--------------------------------
Object arrays that contain themselves no longer cause a recursion error.
Expand Down
10000 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 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
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;
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we should be using div, ldiv, and lldiv here?

Copy link
Member

Choose a reason for hiding this comment

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

Scrap that, apparently it's better to leave the compiler to it. Might be sensible to have a const @type@ quo = in1 / in2; line right beside this one though, just to help it out

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed, that is definitely clearer. Done.

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
18 changes: 18 additions & 0 deletions numpy/core/tests/test_umath.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,13 @@ def test_remainder_basic(self):
else:
assert_(b > rem >= 0, msg)

div, rem = np.divmod(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
# holds for the same integers scaled by powers of two.
Expand All @@ -312,6 +319,10 @@ def test_float_remainder_exact(self):
assert_equal(div, tgtdiv, err_msg=msg)
assert_equal(rem, tgtrem, err_msg=msg)

div, rem = np.divmod(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']
Expand All @@ -330,6 +341,13 @@ def test_float_remainder_roundoff(self):
else:
assert_(b > rem >= 0, msg)

div, rem = np.divmod(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_corner_cases(self):
# Check remainder magnitude.
for dt in np.typecodes['Float']:
Expand Down
0