diff options
author | Matti Picus <matti.picus@gmail.com> | 2019-01-21 08:39:49 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-01-21 08:39:49 +0200 |
commit | 3cbc11ac56054ad3ac7461e57433aefe37f2e3e4 (patch) | |
tree | 59d0b11caf42d5a55538ba69a00c9171cb1064b1 | |
parent | 74f3d07ab0bcfd63f42f62b26eeb6ce68efd4f21 (diff) | |
parent | 4b81a240a4ffffea8a502afbdea43d8bf2991228 (diff) | |
download | numpy-3cbc11ac56054ad3ac7461e57433aefe37f2e3e4.tar.gz |
Merge pull request #12683 from tylerjereddy/timedelta64_divmod
ENH: add mm->qm divmod
-rw-r--r-- | doc/release/1.17.0-notes.rst | 10 | ||||
-rw-r--r-- | numpy/core/code_generators/generate_umath.py | 3 | ||||
-rw-r--r-- | numpy/core/src/umath/loops.c.src | 41 | ||||
-rw-r--r-- | numpy/core/src/umath/loops.h.src | 3 | ||||
-rw-r--r-- | numpy/core/src/umath/ufunc_type_resolution.c | 49 | ||||
-rw-r--r-- | numpy/core/src/umath/ufunc_type_resolution.h | 7 | ||||
-rw-r--r-- | numpy/core/tests/test_datetime.py | 49 |
7 files changed, 155 insertions, 7 deletions
diff --git a/doc/release/1.17.0-notes.rst b/doc/release/1.17.0-notes.rst index 73de0b148..4bdb812d7 100644 --- a/doc/release/1.17.0-notes.rst +++ b/doc/release/1.17.0-notes.rst @@ -50,6 +50,11 @@ identity, it is necessary to also pass in an initial value (e.g., These functions now accept a ``hermitian`` argument, matching the one added to ``np.linalg.matrix_rank`` in 1.14.0. +divmod operation is now supported for two ``timedelta64`` operands +------------------------------------------------------------------ +The divmod operator now handles two ``np.timedelta64`` operands, with +type signature mm->qm. + Improvements ============ @@ -112,3 +117,8 @@ Changes `numpy.median`, `numpy.percentile`, and `numpy.quantile` used to emit a ``RuntimeWarning`` when encountering an `numpy.nan`. Since they return the ``nan`` value, the warning is redundant and has been removed. + +``timedelta64 % 0`` behavior adjusted to return ``NaT`` +------------------------------------------------------- +The modulus operation with two ``np.timedelta64`` operands now returns +``NaT`` in the case of division by zero, rather than returning zero diff --git a/numpy/core/code_generators/generate_umath.py b/numpy/core/code_generators/generate_umath.py index 108fff631..0fac9b05e 100644 --- a/numpy/core/code_generators/generate_umath.py +++ b/numpy/core/code_generators/generate_umath.py @@ -802,8 +802,9 @@ defdict = { 'divmod': Ufunc(2, 2, None, docstrings.get('numpy.core.umath.divmod'), - None, + 'PyUFunc_DivmodTypeResolver', TD(intflt), + [TypeDescription('m', FullTypeDescr, 'mm', 'qm')], # TD(O, f='PyNumber_Divmod'), # gh-9730 ), 'hypot': diff --git a/numpy/core/src/umath/loops.c.src b/numpy/core/src/umath/loops.c.src index 6accf3065..5267be261 100644 --- a/numpy/core/src/umath/loops.c.src +++ b/numpy/core/src/umath/loops.c.src @@ -1623,7 +1623,7 @@ TIMEDELTA_mm_m_remainder(char **args, npy_intp *dimensions, npy_intp *steps, voi else { if (in2 == 0) { npy_set_floatstatus_divbyzero(); - *((npy_timedelta *)op1) = 0; + *((npy_timedelta *)op1) = NPY_DATETIME_NAT; } else { /* handle mixed case the way Python does */ @@ -1647,18 +1647,49 @@ TIMEDELTA_mm_q_floor_divide(char **args, npy_intp *dimensions, npy_intp *steps, const npy_timedelta in2 = *(npy_timedelta *)ip2; if (in1 == NPY_DATETIME_NAT || in2 == NPY_DATETIME_NAT) { npy_set_floatstatus_invalid(); - *((npy_timedelta *)op1) = 0; + *((npy_int64 *)op1) = 0; } else if (in2 == 0) { npy_set_floatstatus_divbyzero(); - *((npy_timedelta *)op1) = 0; + *((npy_int64 *)op1) = 0; } else { if (((in1 > 0) != (in2 > 0)) && (in1 % in2 != 0)) { - *((npy_timedelta *)op1) = in1/in2 - 1; + *((npy_int64 *)op1) = in1/in2 - 1; + } + else { + *((npy_int64 *)op1) = in1/in2; + } + } + } +} + +NPY_NO_EXPORT void +TIMEDELTA_mm_qm_divmod(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) +{ + BINARY_LOOP_TWO_OUT { + const npy_timedelta in1 = *(npy_timedelta *)ip1; + const npy_timedelta in2 = *(npy_timedelta *)ip2; + if (in1 == NPY_DATETIME_NAT || in2 == NPY_DATETIME_NAT) { + npy_set_floatstatus_invalid(); + *((npy_int64 *)op1) = 0; + *((npy_timedelta *)op2) = NPY_DATETIME_NAT; + } + else if (in2 == 0) { + npy_set_floatstatus_divbyzero(); + *((npy_int64 *)op1) = 0; + *((npy_timedelta *)op2) = NPY_DATETIME_NAT; + } + else { + const npy_int64 quo = in1 / in2; + const npy_timedelta rem = in1 % in2; + if ((in1 > 0) == (in2 > 0) || rem == 0) { + *((npy_int64 *)op1) = quo; + *((npy_timedelta *)op2) = rem; } else { - *((npy_timedelta *)op1) = in1/in2; + *((npy_int64 *)op1) = quo - 1; + *((npy_timedelta *)op2) = rem + in2; } } } diff --git a/numpy/core/src/umath/loops.h.src b/numpy/core/src/umath/loops.h.src index 3c908121e..5264a6533 100644 --- a/numpy/core/src/umath/loops.h.src +++ b/numpy/core/src/umath/loops.h.src @@ -479,6 +479,9 @@ TIMEDELTA_mm_q_floor_divide(char **args, npy_intp *dimensions, npy_intp *steps, NPY_NO_EXPORT void TIMEDELTA_mm_m_remainder(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); +NPY_NO_EXPORT void +TIMEDELTA_mm_qm_divmod(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)); + /* Special case equivalents to above functions */ #define TIMEDELTA_mq_m_true_divide TIMEDELTA_mq_m_divide diff --git a/numpy/core/src/umath/ufunc_type_resolution.c b/numpy/core/src/umath/ufunc_type_resolution.c index e2f4d8005..a4a59faa9 100644 --- a/numpy/core/src/umath/ufunc_type_resolution.c +++ b/numpy/core/src/umath/ufunc_type_resolution.c @@ -2357,3 +2357,52 @@ type_tuple_type_resolver(PyUFuncObject *self, return -1; } + +NPY_NO_EXPORT int +PyUFunc_DivmodTypeResolver(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes) +{ + int type_num1, type_num2; + int i; + + type_num1 = PyArray_DESCR(operands[0])->type_num; + type_num2 = PyArray_DESCR(operands[1])->type_num; + + /* Use the default when datetime and timedelta are not involved */ + if (!PyTypeNum_ISDATETIME(type_num1) && !PyTypeNum_ISDATETIME(type_num2)) { + return PyUFunc_DefaultTypeResolver(ufunc, casting, operands, + type_tup, out_dtypes); + } + if (type_num1 == NPY_TIMEDELTA) { + if (type_num2 == NPY_TIMEDELTA) { + out_dtypes[0] = PyArray_PromoteTypes(PyArray_DESCR(operands[0]), + PyArray_DESCR(operands[1])); + out_dtypes[1] = out_dtypes[0]; + Py_INCREF(out_dtypes[1]); + out_dtypes[2] = PyArray_DescrFromType(NPY_LONGLONG); + Py_INCREF(out_dtypes[2]); + out_dtypes[3] = out_dtypes[0]; + Py_INCREF(out_dtypes[3]); + } + else { + return raise_binary_type_reso_error(ufunc, operands); + } + } + else { + return raise_binary_type_reso_error(ufunc, operands); + } + + /* Check against the casting rules */ + if (PyUFunc_ValidateCasting(ufunc, casting, operands, out_dtypes) < 0) { + for (i = 0; i < 4; ++i) { + Py_DECREF(out_dtypes[i]); + out_dtypes[i] = NULL; + } + return -1; + } + + return 0; +} diff --git a/numpy/core/src/umath/ufunc_type_resolution.h b/numpy/core/src/umath/ufunc_type_resolution.h index 2f37af753..78313b1ef 100644 --- a/numpy/core/src/umath/ufunc_type_resolution.h +++ b/numpy/core/src/umath/ufunc_type_resolution.h @@ -99,6 +99,13 @@ PyUFunc_RemainderTypeResolver(PyUFuncObject *ufunc, PyObject *type_tup, PyArray_Descr **out_dtypes); +NPY_NO_EXPORT int +PyUFunc_DivmodTypeResolver(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes); + /* * Does a linear search for the best inner loop of the ufunc. * diff --git a/numpy/core/tests/test_datetime.py b/numpy/core/tests/test_datetime.py index cb7555a34..9832b4275 100644 --- a/numpy/core/tests/test_datetime.py +++ b/numpy/core/tests/test_datetime.py @@ -1161,6 +1161,53 @@ class TestDateTime(object): with assert_raises_regex(TypeError, "common metadata divisor"): val1 // val2 + @pytest.mark.parametrize("op1, op2", [ + # reuse the test cases from floordiv + (np.timedelta64(7, 's'), + np.timedelta64(4, 's')), + # m8 same units round down with negative + (np.timedelta64(7, 's'), + np.timedelta64(-4, 's')), + # m8 same units negative no round down + (np.timedelta64(8, 's'), + np.timedelta64(-4, 's')), + # m8 different units + (np.timedelta64(1, 'm'), + np.timedelta64(31, 's')), + # m8 generic units + (np.timedelta64(1890), + np.timedelta64(31)), + # Y // M works + (np.timedelta64(2, 'Y'), + np.timedelta64('13', 'M')), + # handle 1D arrays + (np.array([1, 2, 3], dtype='m8'), + np.array([2], dtype='m8')), + ]) + def test_timedelta_divmod(self, op1, op2): + expected = (op1 // op2, op1 % op2) + assert_equal(divmod(op1, op2), expected) + + @pytest.mark.parametrize("op1, op2", [ + # reuse cases from floordiv + # div by 0 + (np.timedelta64(10, 'us'), + np.timedelta64(0, 'us')), + # div with NaT + (np.timedelta64('NaT'), + np.timedelta64(50, 'us')), + # special case for int64 min + # in integer floor division + (np.timedelta64(np.iinfo(np.int64).min), + np.timedelta64(-1)), + ]) + def test_timedelta_divmod_warnings(self, op1, op2): + with assert_warns(RuntimeWarning): + expected = (op1 // op2, op1 % op2) + with assert_warns(RuntimeWarning): + actual = divmod(op1, op2) + assert_equal(actual, expected) + def test_datetime_divide(self): for dta, tda, tdb, tdc, tdd in \ [ @@ -1758,7 +1805,7 @@ class TestDateTime(object): def test_timedelta_modulus_div_by_zero(self): with assert_warns(RuntimeWarning): actual = np.timedelta64(10, 's') % np.timedelta64(0, 's') - assert_equal(actual, np.timedelta64(0, 's')) + assert_equal(actual, np.timedelta64('NaT')) @pytest.mark.parametrize("val1, val2", [ # cases where one operand is not |