Skip to content

Commit 54ca820

Browse files
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.
1 parent 3ae9866 commit 54ca820

File tree

8 files changed

+98
-8
lines changed

8 files changed

+98
-8
lines changed

Doc/library/decimal.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,13 @@ Decimal objects
621621
Return :const:`True` if the argument is either positive or negative
622622
infinity and :const:`False` otherwise.
623623

624+
.. method:: is_integer()
625+
626+
Return :const:`True` if the argument is a finite integral value and
627+
:const:`False` otherwise.
628+
629+
.. versionadded:: 3.10
630+
624631
.. method:: is_nan()
625632

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

12171224

1225+
.. method:: is_integer(x)
1226+
1227+
Returns ``True`` if *x* is finite and integral; otherwise
1228+
returns ``False``.
1229+
1230+
.. versionadded:: 3.10
1231+
12181232
.. method:: is_nan(x)
12191233

12201234
Returns ``True`` if *x* is a qNaN or sNaN; otherwise returns ``False``.

Lib/_pydecimal.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3164,6 +3164,12 @@ def is_zero(self):
31643164
"""Return True if self is a zero; otherwise return False."""
31653165
return not self._is_special and self._int == '0'
31663166

3167+
def is_integer(self):
3168+
"""Return True is self is finite and integral; otherwise False."""
3169+
if self._is_special:
3170+
return False
3171+
return self.to_integral_value(rounding=ROUND_FLOOR) == self
3172+
31673173
def _ln_exp_bound(self):
31683174
"""Compute a lower bound for the adjusted exponent of self.ln().
31693175
In other words, compute r such that self.ln() >= 10**r. Assumes
@@ -4659,6 +4665,25 @@ def is_zero(self, a):
46594665
a = _convert_other(a, raiseit=True)
46604666
return a.is_zero()
46614667

4668+
def is_integer(self, a):
4669+
"""Return True if the operand is integral; otherwise return False.
4670+
4671+
>>> ExtendedContext.is_integer(Decimal('0'))
4672+
True
4673+
>>> ExtendedContext.is_integer(Decimal('2.50'))
4674+
False
4675+
>>> ExtendedContext.is_integer(Decimal('-0E+2'))
4676+
True
4677+
>>> ExtendedContext.is_integer(Decimal('-0.5'))
4678+
False
4679+
>>> ExtendedContext.is_integer(Decimal('NaN'))
4680+
False
4681+
>>> ExtendedContext.is_integer(10)
4682+
True
4683+
"""
4684+
a = _convert_other(a, raiseit=True)
4685+
return a.is_integer()
4686+
46624687
def ln(self, a):
46634688
"""Returns the natural (base e) logarithm of the operand.
46644689

Lib/test/decimaltestdata/extra.decTest

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2346,6 +2346,24 @@ bool2096 iszero sNaN -> 0
23462346
bool2097 iszero -sNaN -> 0
23472347
bool2098 iszero sNaN123 -> 0
23482348
bool2099 iszero -sNaN123 -> 0
2349+
bool2100 is_integer -1.0 -> 1
2350+
bool2101 is_integer 0.0 -> 1
2351+
bool2102 is_integer 1.0 -> 1
2352+
bool2103 is_integer 42 -> 1
2353+
bool2104 is_integer 1e2 -> 1
2354+
bool2105 is_integer 1.5 -> 0
2355+
bool2106 is_integer 1e-2 -> 0
2356+
bool2107 is_integer NaN -> 0
2357+
bool2109 is_integer -NaN -> 0
2358+
bool2110 is_integer NaN123 -> 0
2359+
bool2111 is_integer -NaN123 -> 0
2360+
bool2112 is_integer sNaN -> 0
2361+
bool2113 is_integer -sNaN -> 0
2362+
bool2114 is_integer sNaN123 -> 0
2363+
bool2115 is_integer -sNaN123 -> 0
2364+
bool2116 is_integer Infinity -> 0
2365+
bool2117 is_integer -Infinity -> 0
2366+
23492367

23502368
------------------------------------------------------------------------
23512369
-- The following tests (pwmx0 through pwmx440) are for the --

Lib/test/test_decimal.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ def setUp(self):
276276
'is_snan',
277277
'is_subnormal',
278278
'is_zero',
279+
'is_integer',
279280
'same_quantum')
280281

281282
def read_unlimited(self, v, context):
@@ -2726,6 +2727,7 @@ def test_named_parameters(self):
27262727
self.assertRaises(TypeError, D(1).is_snan, context=xc)
27272728
self.assertRaises(TypeError, D(1).is_signed, context=xc)
27282729
self.assertRaises(TypeError, D(1).is_zero, context=xc)
2730+
self.assertRaises(TypeError, D(1).is_integer, context=xc)
27292731

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

3202+
def test_is_integer(self):
3203+
Decimal = self.decimal.Decimal
3204+
Context = self.decimal.Context
3205+
3206+
c = Context()
3207+
b = c.is_integer(Decimal(10))
3208+
self.assertEqual(c.is_integer(10), b)
3209+
self.assertRaises(TypeError, c.is_integer, '10')
3210+
32003211
def test_ln(self):
32013212
Decimal = self.decimal.Decimal
32023213
Context = self.decimal.Context
@@ -4360,6 +4371,19 @@ def test_implicit_context(self):
43604371
self.assertTrue(Decimal("-1").is_signed())
43614372
self.assertTrue(Decimal("0").is_zero())
43624373
self.assertTrue(Decimal("0").is_zero())
4374+
self.assertTrue(Decimal("-1").is_integer())
4375+
self.assertTrue(Decimal("0").is_integer())
4376+
self.assertTrue(Decimal("1").is_integer())
4377+
self.assertTrue(Decimal("42").is_integer())
4378+
self.assertTrue(Decimal("1e2").is_integer())
4379+
self.assertFalse(Decimal("1.5").is_integer())
4380+
self.assertFalse(Decimal("1e-2").is_integer())
4381+
self.assertFalse(Decimal("NaN").is_integer())
4382+
self.assertFalse(Decimal("-NaN").is_integer())
4383+
self.assertFalse(Decimal("sNaN").is_integer())
4384+
self.assertFalse(Decimal("-sNaN").is_integer())
4385+
self.assertFalse(Decimal("Inf").is_integer())
4386+
self.assertFalse(Decimal("-Inf").is_integer())
43634387

43644388
# Copy
43654389
with localcontext() as c:

Modules/_decimal/_decimal.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4138,6 +4138,7 @@ Dec_BoolFunc(mpd_isqnan)
41384138
Dec_BoolFunc(mpd_issnan)
41394139
Dec_BoolFunc(mpd_issigned)
41404140
Dec_BoolFunc(mpd_iszero)
4141+
Dec_BoolFunc(mpd_isinteger)
41414142

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

47764778
/* Boolean functions, optional context arg */
47774779
{ "is_normal", (PyCFunction)(void(*)(void))dec_mpd_isnormal, METH_VARARGS|METH_KEYWORDS, doc_is_normal },
@@ -5183,6 +5185,7 @@ DecCtx_BoolFunc_NO_CTX(mpd_isqnan)
51835185
DecCtx_BoolFunc_NO_CTX(mpd_issigned)
51845186
DecCtx_BoolFunc_NO_CTX(mpd_issnan)
51855187
DecCtx_BoolFunc_NO_CTX(mpd_iszero)
5188+
DecCtx_BoolFunc_NO_CTX(mpd_isinteger)
51865189

51875190
static PyObject *
51885191
ctx_iscanonical(PyObject *context UNUSED, PyObject *v)
@@ -5464,6 +5467,7 @@ static PyMethodDef context_methods [] =
54645467
{ "is_snan", ctx_mpd_issnan, METH_O, doc_ctx_is_snan },
54655468
{ "is_subnormal", ctx_mpd_issubnormal, METH_O, doc_ctx_is_subnormal },
54665469
{ "is_zero", ctx_mpd_iszero, METH_O, doc_ctx_is_zero },
5470+
{ "is_integer", ctx_mpd_isinteger, METH_O, doc_ctx_is_integer },
54675471

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

60986102
return NULL; /* GCOV_NOT_REACHED */
60996103
}
6100-
6101-

Modules/_decimal/docstrings.h

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,11 @@ Return True if the argument is a (positive or negative) zero and False\n\
260260
otherwise.\n\
261261
\n");
262262

263+
PyDoc_STRVAR(doc_is_integer,
264+
"is_integer($self, /)\n--\n\n\
265+
Return True if the argument is finite and integral, otherwise False.\n\
266+
\n");
267+
263268
PyDoc_STRVAR(doc_ln,
264269
"ln($self, /, context=None)\n--\n\n\
265270
Return the natural (base e) logarithm of the operand. The function always\n\
@@ -685,6 +690,11 @@ PyDoc_STRVAR(doc_ctx_is_zero,
685690
Return True if x is a zero, False otherwise.\n\
686691
\n");
687692

693+
PyDoc_STRVAR(doc_ctx_is_integer,
694+
"is_integer($self, x, /)\n--\n\n\
695+
+Return True if x is finite and integral, False otherwise.\n\
696+
+\n");
697+
688698
PyDoc_STRVAR(doc_ctx_ln,
689699
"ln($self, x, /)\n--\n\n\
690700
Return the natural (base e) logarithm of x.\n\
@@ -879,6 +889,3 @@ Convert a number to a string using scientific notation.\n\
879889

880890

881891
#endif /* DOCSTRINGS_H */
882-
883-
884-

Objects/clinic/longobject.c.h

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Objects/longobject.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5241,7 +5241,7 @@ Returns True for all integers.
52415241

52425242
static PyObject *
52435243
int_is_integer_impl(PyObject *self)
5244-
/*[clinic end generated code: output=90f8e794ce5430ef input=903121d57b734c35]*/
5244+
/*[clinic end generated code: output=90f8e794ce5430ef input=1c1a86957301d26d]*/
52455245
{
52465246
Py_RETURN_TRUE;
52475247
}

0 commit comments

Comments
 (0)