Pradyot
Pradyot

Reputation: 3059

python Decimal - checking if integer

I am using the Decimal library in Python, and printing out the values using format(value, 'f'), where value is a Decimal. I get numbers in the form 10.00000, which reflects the precision on the decimal. I know that float supports is_integer, but there seems to be a lack of a similar API for decimals. I was wondering if there was a way around this.

Upvotes: 41

Views: 35627

Answers (9)

Eugene Yarmash
Eugene Yarmash

Reputation: 149736

Yet another way to do it is to use the quantize() method which is generally used to round decimals:

>>> from decimal import Decimal
>>> for d in (Decimal('2.72'), Decimal('3.14'), Decimal('3.0'), Decimal('3')):
...     d == d.quantize(Decimal('1'))
... 
False
False
True
True

Upvotes: 0

Dave
Dave

Reputation: 454

As of Python 3.6, Decimal has a method as_integer_ratio().

as_integer_ratio() returns a (numerator, denominator) tuple. If the denominator is 1, then the value is an integer.

>>> from decimal import Decimal
>>> Decimal("123.456").as_integer_ratio()[1] == 1
False
>>> Decimal("123.000").as_integer_ratio()[1] == 1
True

Upvotes: 16

Laurent LAPORTE
Laurent LAPORTE

Reputation: 22942

The mathematical solution is to convert your decimal number to integer and then test its equality with your number.

Since Decimal can have an arbitrary precision, you should not convert it to int or float.

Fortunately, the Decimalclass has a to_integral_value which make the conversion for you. You can adopt a solution like this:

def is_integer(d):
    return d == d.to_integral_value()

Example:

from decimal import Decimal

d_int = Decimal(3)
assert is_integer(d_int)

d_float = Decimal(3.1415)
assert not is_integer(d_float)

Upvotes: 8

Marko
Marko

Reputation: 45

Building on what was said above, I used:

>>> not 2.5 % 1
False
>>> not 1.0 % 1
True
>>> not 14.000001 % 1
False
>>> not 2.00000000 % 1
True

So you can use the following one liner:

not value % 1

It will provide you with your desired bool.

Upvotes: 1

bits
bits

Reputation: 1705

Decimal class has a function to_integral_exact which converts decimal to an integer and also signals if there were any non-zero digits discarded. https://docs.python.org/3/library/decimal.html#decimal.Decimal.to_integral_exact

Using this information we can implment float's is_integer for Decimal too:

import decimal
def is_integer(value: decimal.Decimal) -> bool:
   is_it = True
   context = decimal.getcontext()
   context.clear_flags()
   exact = value.to_integral_exact()
   if context.flags[decimal.Inexact]:
     is_it = False
   context.clear_flags()
   return is_it

Using the function above:

# tested on Python 3.9
>>> is_integer(decimal.Decimal("0.23"))
False
>>> is_integer(decimal.Decimal("5.0000"))
True
>>> is_integer(decimal.Decimal("5.0001"))
False

Upvotes: 0

direvus
direvus

Reputation: 392

You can call as_tuple() on a Decimal object to get the sign, the sequence of digits, and the exponent which together define the Decimal value.

If the exponent of a normalized Decimal is non-negative, then your value doesn't have a fractional component, i.e., it is an integer. So you can check for this very easily:

def is_integer(dec):
    """True if the given Decimal value is an integer, False otherwise."""
    return dec.normalize().as_tuple()[2] >= 0

Try it and see:

from decimal import Decimal


decimals = [
    Decimal('0'),
    Decimal('0.0000'),
    Decimal('1'),
    Decimal('-1'),
    Decimal('1000000'),
    Decimal('0.1'),
    Decimal('-0.0000000009'),
    Decimal('32.4')]

for d in decimals:
    print("Is {} an integer? {}".format(d, is_integer(d)))
Is 0 an integer? True
Is 0.0000 an integer? True
Is 1 an integer? True
Is -1 an integer? True
Is 1000000 an integer? True
Is 0.1 an integer? False
Is -9E-10 an integer? False
Is 32.4 an integer? False

Upvotes: 3

poke
poke

Reputation: 387557

You could use the modulo operation to check if there is a non-integer remainder:

>>> from decimal import Decimal
>>> Decimal('3.14') % 1 == 0
False
>>> Decimal('3') % 1 == 0
True
>>> Decimal('3.0') % 1 == 0
True

Upvotes: 56

Mike Driscoll
Mike Driscoll

Reputation: 33071

Decimal does have a "hidden" method called _isinteger() that works kind of the like the float's is_integer() method:

>>> Decimal(1)._isinteger()
True
>>> Decimal(1.1)._isinteger()
Traceback (most recent call last):
  File "C:\Program Files (x86)\Wing IDE 4.1\src\debug\tserver\_sandbox.py", line 1, in <module>
    # Used internally for debug sandbox under external interpreter
  File "C:\Python26\Lib\decimal.py", line 649, in __new__
    "First convert the float to a string")
TypeError: Cannot convert float to Decimal.  First convert the float to a string

As you can see, you would have to catch an exception though. Alternatively, you could do the test on the value BEFORE you pass it to Decimal using the float's method as you mentioned or by using isinstance.

Upvotes: 1

willy
willy

Reputation: 1490

Try math.floor(val) == val or val == int(val).

Upvotes: 8

Related Questions