Я хочу различать такие числа, как
2.0
или 2
и действительное дробное число, такое как 2.4
. Как лучше всего это сделать? В настоящее время занимаюсь:
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')
>>>
Почему бы не проверить, не равна ли разность между усечением до целого числа и точным значением нулю?
is_frac = lambda x: int(x)-x != 0
Эта операция встроена:
>>> 5.0.is_integer()
True
>>> 5.00000001.is_integer()
False
>>> 4.9999999.is_integer()
False
Документация - здесь.
ДОБАВЛЕНИЕ
Первоначальное решение работает только для float
. Вот более полный ответ с тестами:
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")
Проблема с этим подходом в том, что я часто имею дело с десятичными знаками, поэтому мне нужно сначала использовать его. В противном случае получаю: AttributeError: 'decimal.Decimal' object has no attribute 'is_integer'
Хорошая точка зрения. is_integer
существует только на float
. Для Decimal
я бы выбрал d.as_integer_ratio()[1] == 1
.
Обновил ответ
Ах, as_integer_ratio
лучше, чем as_tuple
, с подсчетом нулей в конце. +1
Думаю, да ... Я попробовал несколько методов decimal.Decimal
и обнаружил, что этот метод особенно хорош и работает для всего, что я к нему применил. Неограниченные целые числа в Python так хороши.
@RayToal дополнительно, почему происходит что-то вроде >>> float(-9999999999999999999999)==int(-9999999999999999999999) >>> False
?
Поплавки не имеют произвольной точности! Они хранятся в 64-битном формате с 53 битами мантиссы, что составляет 15-17 значащих цифр. Если вы оцените float(-9999999999999999999999)
на Python, вы получите -1e+22
. Значение -9999999999999999999999 просто не может быть представлено в 64-битном IEEE 754. Ближайшее представимое значение - -10000000000000000000000. Но в типе int
все числа представимы.
Если вы имеете дело с модулем decimal
или объектом float
, вы можете сделать это легко:
def is_factional(num):
return isinstance(num, (float, Decimal))
Если некоторые из ваших чисел являются числами decimal.Decimal
, у них могут быть проблемы с диапазоном, когда преобразование в число с плавающей запятой не удается или отбрасывает существующую дробную часть, в зависимости от их точности:
>>> 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
Метод str
обычно работает (по модулю проблемных случаев, подобных показанному выше), поэтому вы можете придерживаться его, но может быть лучше попытаться использовать is_integer
и использовать запасной вариант, если это не поможет:
try:
return x.is_integer()
except AttributeError:
pass
(как отмечают другие, вам также необходимо проверить здесь int
и long
, если это разрешенные типы, поскольку они являются целыми числами по определению, но не имеют атрибута is_integer
).
На этом этапе стоит рассмотреть все остальные ответы, но вот конкретный обработчик decimal.Decimal:
# 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
Вот один из способов сделать это (при условии, например, что 2/2 не является «дробным» в том смысле, который вы имеете в виду):
# 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]))
Это будет работать, только если n
- это int
, float
или decimal.Decimal
. Но вы можете расширить его для обработки других числовых типов, таких как numpy.float64
или numpy.int32
, просто включив их в numeric_types
.
Python включает модуль fractions
, который генерирует дроби (рациональные числа) из строк, float
, целых чисел и многого другого. Просто создайте Fraction
и проверьте, отличен ли его знаменатель от 1 (конструктор Fraction
автоматически уменьшит число до наименьших членов):
from fractions import Fraction
def is_fractional(num):
return Fraction(num).denominator != 1
Обратите внимание, что описанный выше метод может вызвать исключение, если преобразование в Fraction завершится неудачно. В этом случае неизвестно, является ли объект дробным.
Может
abs(x - int(x)) > 0
?