David542
David542

Reputation: 110382

Best way to see if a number is fractional or not

I'm looking to differentiate between a number like 2.0 or 2 and an actual fractional number such as 2.4. What would be the best way to do this? Currently I'm doing:

def is_fractional(num):
    if not str(num).replace('.','').isdigit(): return
    return float(num) != int(num)

>>> is_fractional(2)
False
>>> is_fractional(2.1)
True
>>> is_fractional(2.0)
False
>>> is_fractional('a')
>>>

Upvotes: 5

Views: 2850

Answers (6)

Peter O.
Peter O.

Reputation: 32898

Python includes a fractions module that generates fractions (rational numbers) from strings, floats, integers, and much more. Just create a Fraction and check whether its denominator is other than 1 (the Fraction constructor will automatically reduce the number to lowest terms):

from fractions import Fraction

def is_fractional(num):
    return Fraction(num).denominator != 1

Note that the method above may raise an exception if the conversion to a Fraction fails. In this case, it's not known whether the object is fractional.

Upvotes: 1

Ray Toal
Ray Toal

Reputation: 88428

That operation is built-in:

>>> 5.0.is_integer()
True
>>> 5.00000001.is_integer()
False
>>> 4.9999999.is_integer()
False

Documentation is here.

ADDENDUM

The initial solution only works for float. Here's a more complete answer, with tests:

from decimal import Decimal

def is_integer(x):
    if isinstance(x, int):
        return True
    elif isinstance(x, float):
        return x.is_integer()
    elif isinstance(x, Decimal):
        return x.as_integer_ratio()[1] == 1
    return False

good = [
    0, 
    0.0, 
    3, 
    -9999999999999999999999, 
    -2.0000000000000,
    Decimal("3.000000"),
    Decimal("-9")
]
bad = [
    -9.99999999999999,
    "dogs",
    Decimal("-4.00000000000000000000000000000000001"),
    Decimal("0.99999999999999999999999999999999999")
]

for x in good:
    assert is_integer(x)
for x in bad:
    assert not is_integer(x)
print("All tests passed")

Upvotes: 11

lefft
lefft

Reputation: 2105

Here is one way to do it (assuming e.g. 2/2 is not "fractional" in the sense you have in mind):

# could also extend to other numeric types numpy.float32 
from decimal import Decimal

def is_frac(n):
  numeric_types = (int, float, Decimal)
  assert isinstance(n, numeric_types), 'n must be numeric :/'
  # (ints are never fractions)
  if type(n) is int: return False
  return n != float(int(n))

# various sorts of numbers 
ns = [-1, -1.0, 0, 0.1, 1, 1.0, 1., 2.3, 1e0, 1e3, 1.1e3, 
      Decimal(3), Decimal(3.0), Decimal(3.1)]

# confirm that values are as expected 
dict(zip(ns, [is_frac(n) for n in ns]))

This will only work if n is an int or a float or decimal.Decimal. But you could extend it to handle other numeric types such as numpy.float64 or numpy.int32 by just including them in numeric_types.

Upvotes: 0

torek
torek

Reputation: 489253

If some of your numbers are decimal.Decimals, they might have range issues where conversion to float fails, or drops the fractional part that actually exists, depending on their precision:

>>> import decimal
>>> x = decimal.Decimal('1.00000000000000000000000000000000000001')
>>> str(x)
'1.00000000000000000000000000000000000001'
>>> float(x).is_integer()
True

>>> y = decimal.Decimal('1e5000')
>>> str(y)
'1E+5000'
>>> float(y)
inf

The str method will generally work (modulo problem cases like the one illustrated above), so you could stick with that, but it might be better to attempt to use is_integer and use a fallback if that fails:

try:
   return x.is_integer()
except AttributeError:
   pass

(as others note, you'll need to check for int and long here as well, if those are allowed types, since they are integers by definition but lack an is_integer attribute).

At this point, it's worth considering all of the other answers, but here's a specific decimal.Decimal handler:

# optional: special case decimal.Decimal here
try:
    as_tuple = x.as_tuple()
    trailing0s = len(list(itertools.takewhile(lambda i: i == 0, reversed(as_tuple[1]))))
    return as_tuple[2] + trailing0s < 0
except (AttributeError, IndexError): # no as_tuple, or not 3 elements long, etc
    pass

Upvotes: 3

Moige
Moige

Reputation: 46

If you are dealing with decimal module or with a float object, you can do this easily:

def is_factional(num):
    return isinstance(num, (float, Decimal))

Upvotes: 0

Alex Foglia
Alex Foglia

Reputation: 530

Why do not check if the difference between the truncation to integer and the exact value is not zero?

is_frac = lambda x: int(x)-x != 0

Upvotes: 3

Related Questions