summaryrefslogtreecommitdiff
path: root/numpy
diff options
context:
space:
mode:
Diffstat (limited to 'numpy')
-rw-r--r--numpy/__init__.pyi3
-rw-r--r--numpy/core/_add_newdocs_scalars.py45
-rw-r--r--numpy/core/src/multiarray/scalartypes.c.src49
-rw-r--r--numpy/core/tests/test_scalar_methods.py26
-rw-r--r--numpy/typing/tests/data/fail/scalars.py1
-rw-r--r--numpy/typing/tests/data/reveal/scalars.py2
6 files changed, 117 insertions, 9 deletions
diff --git a/numpy/__init__.pyi b/numpy/__init__.pyi
index 2e7d90d68..b34cdecc7 100644
--- a/numpy/__init__.pyi
+++ b/numpy/__init__.pyi
@@ -3203,6 +3203,7 @@ class integer(number[_NBit1]): # type: ignore
self, args: L[0] | Tuple[()] | Tuple[L[0]] = ..., /,
) -> int: ...
def tolist(self) -> int: ...
+ def is_integer(self) -> L[True]: ...
def __index__(self) -> int: ...
__truediv__: _IntTrueDiv[_NBit1]
__rtruediv__: _IntTrueDiv[_NBit1]
@@ -3356,7 +3357,7 @@ class floating(inexact[_NBit1]):
/,
) -> float: ...
def tolist(self) -> float: ...
- def is_integer(self: float64) -> bool: ...
+ def is_integer(self) -> bool: ...
def hex(self: float64) -> str: ...
@classmethod
def fromhex(cls: Type[float64], string: str, /) -> float64: ...
diff --git a/numpy/core/_add_newdocs_scalars.py b/numpy/core/_add_newdocs_scalars.py
index 602b1db6e..8773d6c96 100644
--- a/numpy/core/_add_newdocs_scalars.py
+++ b/numpy/core/_add_newdocs_scalars.py
@@ -205,12 +205,12 @@ add_newdoc_for_scalar_type('bytes_', ['string_'],
add_newdoc_for_scalar_type('void', [],
r"""
Either an opaque sequence of bytes, or a structure.
-
+
>>> np.void(b'abcd')
void(b'\x61\x62\x63\x64')
-
+
Structured `void` scalars can only be constructed via extraction from :ref:`structured_arrays`:
-
+
>>> arr = np.array((1, 2), dtype=[('x', np.int8), ('y', np.int8)])
>>> arr[()]
(1, 2) # looks like a tuple, but is `np.void`
@@ -226,20 +226,36 @@ add_newdoc_for_scalar_type('datetime64', [],
>>> np.datetime64(10, 'Y')
numpy.datetime64('1980')
>>> np.datetime64('1980', 'Y')
- numpy.datetime64('1980')
+ numpy.datetime64('1980')
>>> np.datetime64(10, 'D')
numpy.datetime64('1970-01-11')
-
+
See :ref:`arrays.datetime` for more information.
""")
add_newdoc_for_scalar_type('timedelta64', [],
"""
A timedelta stored as a 64-bit integer.
-
+
See :ref:`arrays.datetime` for more information.
""")
+add_newdoc('numpy.core.numerictypes', "integer", ('is_integer',
+ """
+ integer.is_integer() -> bool
+
+ Return ``True`` if the number is finite with integral value.
+
+ .. versionadded:: 1.22
+
+ Examples
+ --------
+ >>> np.int64(-2).is_integer()
+ True
+ >>> np.uint32(5).is_integer()
+ True
+ """))
+
# TODO: work out how to put this on the base class, np.floating
for float_name in ('half', 'single', 'double', 'longdouble'):
add_newdoc('numpy.core.numerictypes', float_name, ('as_integer_ratio',
@@ -257,3 +273,20 @@ for float_name in ('half', 'single', 'double', 'longdouble'):
>>> np.{ftype}(-.25).as_integer_ratio()
(-1, 4)
""".format(ftype=float_name)))
+
+ add_newdoc('numpy.core.numerictypes', float_name, ('is_integer',
+ f"""
+ {float_name}.is_integer() -> bool
+
+ Return ``True`` if the floating point number is finite with integral
+ value, and ``False`` otherwise.
+
+ .. versionadded:: 1.22
+
+ Examples
+ --------
+ >>> np.{float_name}(-2.0).is_integer()
+ True
+ >>> np.{float_name}(3.2).is_integer()
+ False
+ """))
diff --git a/numpy/core/src/multiarray/scalartypes.c.src b/numpy/core/src/multiarray/scalartypes.c.src
index 40f736125..740ec8cc2 100644
--- a/numpy/core/src/multiarray/scalartypes.c.src
+++ b/numpy/core/src/multiarray/scalartypes.c.src
@@ -1908,6 +1908,39 @@ error:
}
/**end repeat**/
+/**begin repeat
+ * #name = half, float, double, longdouble#
+ * #Name = Half, Float, Double, LongDouble#
+ * #is_half = 1,0,0,0#
+ * #c = f, f, , l#
+ */
+static PyObject *
+@name@_is_integer(PyObject *self)
+{
+#if @is_half@
+ npy_double val = npy_half_to_double(PyArrayScalar_VAL(self, @Name@));
+#else
+ npy_@name@ val = PyArrayScalar_VAL(self, @Name@);
+#endif
+ PyObject *ret;
+
+ if (npy_isnan(val)) {
+ Py_RETURN_FALSE;
+ }
+ if (!npy_isfinite(val)) {
+ Py_RETURN_FALSE;
+ }
+
+ ret = (npy_floor@c@(val) == val) ? Py_True : Py_False;
+ Py_INCREF(ret);
+ return ret;
+}
+/**end repeat**/
+
+static PyObject *
+integer_is_integer(PyObject *self) {
+ Py_RETURN_TRUE;
+}
/*
* need to fill in doc-strings for these methods on import -- copy from
@@ -2167,7 +2200,7 @@ static PyMethodDef @name@type_methods[] = {
/**end repeat**/
/**begin repeat
- * #name = integer,floating, complexfloating#
+ * #name = floating, complexfloating#
*/
static PyMethodDef @name@type_methods[] = {
/* Hook for the round() builtin */
@@ -2178,6 +2211,17 @@ static PyMethodDef @name@type_methods[] = {
};
/**end repeat**/
+static PyMethodDef integertype_methods[] = {
+ /* Hook for the round() builtin */
+ {"__round__",
+ (PyCFunction)integertype_dunder_round,
+ METH_VARARGS | METH_KEYWORDS, NULL},
+ {"is_integer",
+ (PyCFunction)integer_is_integer,
+ METH_NOARGS, NULL},
+ {NULL, NULL, 0, NULL} /* sentinel */
+};
+
/**begin repeat
* #name = half,float,double,longdouble#
*/
@@ -2185,6 +2229,9 @@ static PyMethodDef @name@type_methods[] = {
{"as_integer_ratio",
(PyCFunction)@name@_as_integer_ratio,
METH_NOARGS, NULL},
+ {"is_integer",
+ (PyCFunction)@name@_is_integer,
+ METH_NOARGS, NULL},
{NULL, NULL, 0, NULL}
};
/**end repeat**/
diff --git a/numpy/core/tests/test_scalar_methods.py b/numpy/core/tests/test_scalar_methods.py
index 3693bba59..94b2dd3c9 100644
--- a/numpy/core/tests/test_scalar_methods.py
+++ b/numpy/core/tests/test_scalar_methods.py
@@ -102,3 +102,29 @@ class TestAsIntegerRatio:
pytest.skip("longdouble too small on this platform")
assert_equal(nf / df, f, "{}/{}".format(n, d))
+
+
+class TestIsInteger:
+ @pytest.mark.parametrize("str_value", ["inf", "nan"])
+ @pytest.mark.parametrize("code", np.typecodes["Float"])
+ def test_special(self, code: str, str_value: str) -> None:
+ cls = np.dtype(code).type
+ value = cls(str_value)
+ assert not value.is_integer()
+
+ @pytest.mark.parametrize(
+ "code", np.typecodes["Float"] + np.typecodes["AllInteger"]
+ )
+ def test_true(self, code: str) -> None:
+ float_array = np.arange(-5, 5).astype(code)
+ for value in float_array:
+ assert value.is_integer()
+
+ @pytest.mark.parametrize("code", np.typecodes["Float"])
+ def test_false(self, code: str) -> None:
+ float_array = np.arange(-5, 5).astype(code)
+ float_array *= 1.1
+ for value in float_array:
+ if value == 0:
+ continue
+ assert not value.is_integer()
diff --git a/numpy/typing/tests/data/fail/scalars.py b/numpy/typing/tests/data/fail/scalars.py
index 099418e67..94fe3f71e 100644
--- a/numpy/typing/tests/data/fail/scalars.py
+++ b/numpy/typing/tests/data/fail/scalars.py
@@ -87,7 +87,6 @@ round(c8) # E: No overload variant
c8.__getnewargs__() # E: Invalid self argument
f2.__getnewargs__() # E: Invalid self argument
-f2.is_integer() # E: Invalid self argument
f2.hex() # E: Invalid self argument
np.float16.fromhex("0x0.0p+0") # E: Invalid self argument
f2.__trunc__() # E: Invalid self argument
diff --git a/numpy/typing/tests/data/reveal/scalars.py b/numpy/typing/tests/data/reveal/scalars.py
index c36813004..e83d579e9 100644
--- a/numpy/typing/tests/data/reveal/scalars.py
+++ b/numpy/typing/tests/data/reveal/scalars.py
@@ -156,3 +156,5 @@ reveal_type(round(f8, 3)) # E: {float64}
if sys.version_info >= (3, 9):
reveal_type(f8.__ceil__()) # E: int
reveal_type(f8.__floor__()) # E: int
+
+reveal_type(i8.is_integer()) # E: Literal[True]