8000 bpo-26680: Incorporate is_integer in all built-in and standard library numeric types by rob-smallshire · Pull Request #6121 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

bpo-26680: Incorporate is_integer in all built-in and standard library numeric types #6121

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 10 commits into from
Oct 1, 2020
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
Prev Previous commit
Next Next commit
bpo-26680: Adds Decimal.is_integer to the Python and C implementations.
The C implementation of Decimal already implements and uses
mpd_isinteger internally, we just expose the existing function to
Python.

The Python implementation uses internal conversion to integer
using to_integral_value().

In both cases, the corresponding context methods are also
implemented.

Tests and documentation are included.
  • Loading branch information
10000
rob-smallshire committed Sep 18, 2020
commit 54ca820694afef68f71d1781167efce71e845ab2
14 changes: 14 additions & 0 deletions Doc/library/decimal.rst
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,13 @@ Decimal objects
Return :const:`True` if the argument is either positive or negative
infinity and :const:`False` otherwise.

.. method:: is_integer()

Return :const:`True` if the argument is a finite integral value and
:const:`False` otherwise.

Copy link
Member
@mdickinson mdickinson May 12, 2019

Choose a reason for hiding this comment

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

Please add a .. versionadded:: 3.10 note here and in the other relevant bits of documentation.

EDIT: edited the comment to update the version; the original suggestion of 3.8 is obviously out of date

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

.. versionadded:: 3.10

.. method:: is_nan()

Return :const:`True` if the argument is a (quiet or signaling) NaN and
Expand Down Expand Up @@ -1215,6 +1222,13 @@ In addition to the three supplied contexts, new contexts can be created with the
Returns ``True`` if *x* is infinite; otherwise returns ``False``.


.. method:: is_integer(x)

Returns ``True`` if *x* is finite and integral; otherwise
Copy link
Member

Choose a reason for hiding this comment

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

One day it would be nice to fix all these docstrings for consistency (both with one another and with PEP 257). But not today.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed, when you hop around the code you notice how different they all are. I tried to go for local consistency rather than global consistency.

returns ``False``.

.. versionadded:: 3.10

.. method:: is_nan(x)

Returns ``True`` if *x* is a qNaN or sNaN; otherwise returns ``False``.
Expand Down
25 changes: 25 additions & 0 deletions Lib/_pydecimal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3164,6 +3164,12 @@ def is_zero(self):
"""Return True if self is a zero; otherwise return False."""
return not self._is_special and self._int == '0'

def is_integer(self):
"""Return True is self is finite and integral; otherwise False."""
if self._is_special:
return False
return self.to_integral_value(rounding=ROUND_FLOOR) == self

def _ln_exp_bound(self):
"""Compute a lower bound for the adjusted exponent of self.ln().
In other words, compute r such that self.ln() >= 10**r. Assumes
Expand Down Expand Up @@ -4659,6 +4665,25 @@ def is_zero(self, a):
a = _convert_other(a, raiseit=True)
return a.is_zero()

def is_integer(self, a):
"""Return True if the operand is integral; otherwise return False.

>>> ExtendedContext.is_integer(Decimal('0'))
True
>>> ExtendedContext.is_integer(Decimal('2.50'))
False
>>> ExtendedContext.is_integer(Decimal('-0E+2'))
True
>>> ExtendedContext.is_integer(Decimal('-0.5'))
False
>>> ExtendedContext.is_integer(Decimal('NaN'))
False
>>> ExtendedContext.is_integer(10)
True
"""
a = _convert_other(a, raiseit=True)
return a.is_integer()

def ln(self, a):
"""Returns the natural (base e) logarithm of the operand.

Expand Down
18 changes: 18 additions & 0 deletions Lib/test/decimaltestdata/extra.decTest
Original file line number Diff line number Diff line change
Expand Up @@ -2346,6 +2346,24 @@ bool2096 iszero sNaN -> 0
bool2097 iszero -sNaN -> 0
bool2098 iszero sNaN123 -> 0
bool2099 iszero -sNaN123 -> 0
bool2100 is_integer -1.0 -> 1
bool2101 is_integer 0.0 -> 1
bool2102 is_integer 1.0 -> 1
bool2103 is_integer 42 -> 1
bool2104 is_integer 1e2 -> 1
bool2105 is_integer 1.5 -> 0
bool2106 is_integer 1e-2 -> 0
bool2107 is_integer NaN -> 0
bool2109 is_integer -NaN -> 0
bool2110 is_integer NaN123 -> 0
bool2111 is_integer -NaN123 -> 0
bool2112 is_integer sNaN -> 0
bool2113 is_integer -sNaN -> 0
bool2114 is_integer sNaN123 -> 0
bool2115 is_integer -sNaN123 -> 0
bool2116 is_integer Infinity -> 0
bool2117 is_integer -Infinity -> 0


------------------------------------------------------------------------
-- The following tests (pwmx0 through pwmx440) are for the --
Expand Down
24 changes: 24 additions & 0 deletions Lib/test/test_decimal.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ def setUp(self):
'is_snan',
'is_subnormal',
'is_zero',
'is_integer',
'same_quantum')

def read_unlimited(self, v, context):
Expand Down Expand Up @@ -2726,6 +2727,7 @@ def test_named_parameters(self):
self.assertRaises(TypeError, D(1).is_snan, context=xc)
self.assertRaises(TypeError, D(1).is_signed, context=xc)
self.assertRaises(TypeError, D(1).is_zero, context=xc)
self.assertRaises(TypeError, D(1).is_integer, context=xc)

self.assertFalse(D("0.01").is_normal(context=xc))
self.assertTrue(D("0.01").is_subnormal(context=xc))
Expand Down Expand Up @@ -3197,6 +3199,15 @@ def test_is_zero(self):
self.assertEqual(c.is_zero(10), d)
self.assertRaises(TypeError, c.is_zero, '10')

def test_is_integer(self):
Decimal = self.decimal.Decimal
Context = self.decimal.Context

c = Context()
b = c.is_integer(Decimal(10))
self.assertEqual(c.is_integer(10), b)
self.assertRaises(TypeError, c.is_integer, '10')

def test_ln(self):
Decimal = self.decimal.Decimal
Context = self.decimal.Context
Expand Down Expand Up @@ -4360,6 +4371,19 @@ def test_implicit_context(self):
self.assertTrue(Decimal("-1").is_signed())
self.assertTrue(Decimal("0").is_zero())
self.assertTrue(Decimal("0").is_zero())
self.assertTrue(Decimal("-1").is_integer())
Copy link
Member

Choose a reason for hiding this comment

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

Would it be worth adding a couple of tests for cases where the exponent isn't 0, e.g. Decimal("1e2") and Decimal("100e-2")? We should also test the behaviour for signalling NaNs

Ideally, we'd also test that no Decimal floating-point flags are ever raised. An easy way to do this would be to add some testcases to Lib/test/decimaltestdata/extra.decTest, which will check that exactly the flags mentioned are raised for each given testcase.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

self.assertTrue(Decimal("0").is_integer())
self.assertTrue(Decimal("1").is_integer())
self.assertTrue(Decimal("42").is_integer())
self.assertTrue(Decimal("1e2").is_integer())
self.assertFalse(Decimal("1.5").is_integer())
self.assertFalse(Decimal("1e-2").is_integer())
self.assertFalse(Decimal("NaN").is_integer())
self.assertFalse(Decimal("-NaN").is_integer())
self.assertFalse(Decimal("sNaN").is_integer())
self.assertFalse(Decimal("-sNaN").is_integer())
self.assertFalse(Decimal("Inf").is_integer())
self.assertFalse(Decimal("-Inf").is_integer())

# Copy
with localcontext() as c:
Expand Down
6 changes: 4 additions & 2 deletions Modules/_decimal/_decimal.c
Original file line number Diff line number Diff line change
Expand Up @@ -4138,6 +4138,7 @@ Dec_BoolFunc(mpd_isqnan)
Dec_BoolFunc(mpd_issnan)
Dec_BoolFunc(mpd_issigned)
Dec_BoolFunc(mpd_iszero)
Dec_BoolFunc(mpd_isinteger)

/* Boolean functions, optional context arg */
Dec_BoolFuncVA(mpd_isnormal)
Expand Down Expand Up @@ -4772,6 +4773,7 @@ static PyMethodDef dec_methods [] =
{ "is_snan", dec_mpd_issnan, METH_NOARGS, doc_is_snan },
{ "is_signed", dec_mpd_issigned, METH_NOARGS, doc_is_signed },
{ "is_zero", dec_mpd_iszero, METH_NOARGS, doc_is_zero },
{ "is_integer", dec_mpd_isinteger, METH_NOARGS, doc_is_integer},

/* Boolean functions, optional context arg */
{ "is_normal", (PyCFunction)(void(*)(void))dec_mpd_isnormal, METH_VARARGS|METH_KEYWORDS, doc_is_normal },
Expand Down Expand Up @@ -5183,6 +5185,7 @@ DecCtx_BoolFunc_NO_CTX(mpd_isqnan)
DecCtx_BoolFunc_NO_CTX(mpd_issigned)
DecCtx_BoolFunc_NO_CTX(mpd_issnan)
DecCtx_BoolFunc_NO_CTX(mpd_iszero)
DecCtx_BoolFunc_NO_CTX(mpd_isinteger)

static PyObject *
ctx_iscanonical(PyObject *context UNUSED, PyObject *v)
Expand Down Expand Up @@ -5464,6 +5467,7 @@ static PyMethodDef context_methods [] =
{ "is_snan", ctx_mpd_issnan, METH_O, doc_ctx_is_snan },
{ "is_subnormal", ctx_mpd_issubnormal, METH_O, doc_ctx_is_subnormal },
{ "is_zero", ctx_mpd_iszero, METH_O, doc_ctx_is_zero },
{ "is_integer", ctx_mpd_isinteger, METH_O, doc_ctx_is_integer },

/* Functions with a single decimal argument */
{ "_apply", PyDecContext_Apply, METH_O, NULL }, /* alias for apply */
Expand Down Expand Up @@ -6097,5 +6101,3 @@ PyInit__decimal(void)

return NULL; /* GCOV_NOT_REACHED */
}


13 changes: 10 additions & 3 deletions Modules/_decimal/docstrings.h
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,11 @@ Return True if the argument is a (positive or negative) zero and False\n\
otherwise.\n\
\n");

PyDoc_STRVAR(doc_is_integer,
"is_integer($self, /)\n--\n\n\
Return True if the argument is finite and integral, otherwise False.\n\
\n");

PyDoc_STRVAR(doc_ln,
"ln($self, /, context=None)\n--\n\n\
Return the natural (base e) logarithm of the operand. The function always\n\
Expand Down Expand Up @@ -685,6 +690,11 @@ PyDoc_STRVAR(doc_ctx_is_zero,
Return True if x is a zero, False otherwise.\n\
\n");

PyDoc_STRVAR(doc_ctx_is_integer,
"is_integer($self, x, /)\n--\n\n\
+Return True if x is finite and integral, False otherwise.\n\
+\n");

PyDoc_STRVAR(doc_ctx_ln,
"ln($self, x, /)\n--\n\n\
Return the natural (base e) logarithm of x.\n\
Expand Down Expand Up @@ -879,6 +889,3 @@ Convert a number to a string using scientific notation.\n\


#endif /* DOCSTRINGS_H */



4 changes: 2 additions & 2 deletions Objects/clinic/longobject.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Objects/longobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -5241,7 +5241,7 @@ Returns True for all integers.

static PyObject *
Copy link
Member

Choose a reason for hiding this comment

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

Could we convert this to Argument Clinic? It seems somewhat trivial, but one advantage is having the docstring close to the definition. Another is having docstring consistency between int.is_integer and float.is_integer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

int_is_integer_impl(PyObject *self)
/*[clinic end generated code: output=90f8e794ce5430ef input=903121d57b734c35]*/
/*[clinic end generated code: output=90f8e794ce5430ef input=1c1a86957301d26d]*/
{
Py_RETURN_TRUE;
}
Expand Down
0