diff --git a/doc/release/upcoming_changes/26766.change.rst b/doc/release/upcoming_changes/26766.change.rst new file mode 100644 index 000000000000..923dbe816dd1 --- /dev/null +++ b/doc/release/upcoming_changes/26766.change.rst @@ -0,0 +1,2 @@ +* `numpy.floor`, `numpy.ceil`, and `numpy.trunc` now won't perform casting + to a floating dtype for integer and boolean dtype input arrays. diff --git a/numpy/_core/code_generators/generate_umath.py b/numpy/_core/code_generators/generate_umath.py index 06871a44b37f..6ec19f12e067 100644 --- a/numpy/_core/code_generators/generate_umath.py +++ b/numpy/_core/code_generators/generate_umath.py @@ -953,6 +953,7 @@ def english_upper(s): Ufunc(1, 1, None, docstrings.get('numpy._core.umath.ceil'), None, + TD(bints), TD('e', f='ceil', astype={'e': 'f'}), TD(inexactvec, dispatch=[('loops_unary_fp', 'fd')]), TD('fdg', f='ceil'), @@ -962,6 +963,7 @@ def english_upper(s): Ufunc(1, 1, None, docstrings.get('numpy._core.umath.trunc'), None, + TD(bints), TD('e', f='trunc', astype={'e': 'f'}), TD(inexactvec, dispatch=[('loops_unary_fp', 'fd')]), TD('fdg', f='trunc'), @@ -978,6 +980,7 @@ def english_upper(s): Ufunc(1, 1, None, docstrings.get('numpy._core.umath.floor'), None, + TD(bints), TD('e', f='floor', astype={'e': 'f'}), TD(inexactvec, dispatch=[('loops_unary_fp', 'fd')]), TD('fdg', f='floor'), diff --git a/numpy/_core/src/umath/loops.h.src b/numpy/_core/src/umath/loops.h.src index 3cb689818ec8..f775bc22b8a8 100644 --- a/numpy/_core/src/umath/loops.h.src +++ b/numpy/_core/src/umath/loops.h.src @@ -77,7 +77,7 @@ BOOL_@kind@(char **args, npy_intp const *dimensions, npy_intp const *steps, void #include "loops_autovec.dispatch.h" #endif /**begin repeat - * #kind = isnan, isinf, isfinite# + * #kind = isnan, isinf, isfinite, floor, ceil, trunc# */ NPY_CPU_DISPATCH_DECLARE(NPY_NO_EXPORT void BOOL_@kind@, (char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(func))) @@ -179,6 +179,12 @@ NPY_NO_EXPORT void NPY_NO_EXPORT void @S@@TYPE@_positive(char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(func)); +/**begin repeat2 + * #kind = floor, ceil, trunc# + */ +#define @S@@TYPE@_@kind@ @S@@TYPE@_positive +/**end repeat2**/ + /**begin repeat2 * Arithmetic * #kind = add, subtract, multiply, bitwise_and, bitwise_or, bitwise_xor, diff --git a/numpy/_core/src/umath/loops_autovec.dispatch.c.src b/numpy/_core/src/umath/loops_autovec.dispatch.c.src index 6ccafe577c72..e93e851d6b7a 100644 --- a/numpy/_core/src/umath/loops_autovec.dispatch.c.src +++ b/numpy/_core/src/umath/loops_autovec.dispatch.c.src @@ -264,6 +264,17 @@ NPY_NO_EXPORT void NPY_CPU_DISPATCH_CURFX(BOOL_@kind@) } /**end repeat**/ +/**begin repeat + * Identity + * #kind = floor, ceil, trunc# + */ +NPY_NO_EXPORT void NPY_CPU_DISPATCH_CURFX(BOOL_@kind@) +(char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(func)) +{ + UNARY_LOOP_FAST(npy_bool, npy_bool, *out = in); +} +/**end repeat**/ + /* ***************************************************************************** ** HALF-FLOAT LOOPS ** diff --git a/numpy/_core/tests/test_umath.py b/numpy/_core/tests/test_umath.py index df8ec07dc3f5..548411da7f48 100644 --- a/numpy/_core/tests/test_umath.py +++ b/numpy/_core/tests/test_umath.py @@ -4174,6 +4174,15 @@ def test_fraction(self): assert_equal(np.ceil(f), -1) assert_equal(np.trunc(f), -1) + @pytest.mark.parametrize('func', [np.floor, np.ceil, np.trunc]) + @pytest.mark.parametrize('dtype', [np.bool, np.float64, np.float32, + np.int64, np.uint32]) + def test_output_dtype(self, func, dtype): + arr = np.array([-2, 0, 4, 8]).astype(dtype) + result = func(arr) + assert_equal(arr, result) + assert result.dtype == dtype + class TestComplexFunctions: funcs = [np.arcsin, np.arccos, np.arctan, np.arcsinh, np.arccosh, diff --git a/tools/ci/array-api-skips.txt b/tools/ci/array-api-skips.txt index 5b7324ae753b..2d618ee05d45 100644 --- a/tools/ci/array-api-skips.txt +++ b/tools/ci/array-api-skips.txt @@ -1,11 +1,6 @@ # finfo return type misalignment array_api_tests/test_data_type_functions.py::test_finfo[float32] -# for int inputs out.dtype=float32, but should be int -array_api_tests/test_operators_and_elementwise_functions.py::test_ceil -array_api_tests/test_operators_and_elementwise_functions.py::test_floor -array_api_tests/test_operators_and_elementwise_functions.py::test_trunc - # 'shape' arg is present. 'newshape' is retained for backward compat. array_api_tests/test_signatures.py::test_func_signature[reshape]