Cast to Integer Only If "Lossless"?

Viewed 130

I wish to cast a string or a number to an integer only if the casting is "lossless" or, another way to put it, only if the string or number is indeed an integer.

For instance,

  • 3.0 (a float that is indeed an integer) -> 3.
  • '3.000' (a string that is an integer) -> 3.
  • 3.1 -> exception raised.
  • '4.2' -> exception raised.

Directly doing int(x) will convert 3.1 to 3.

This is the best I have:

def safe_cast_to_int(x):
    int_x = int(x)
    if np.issubdtype(type(x), np.floating):
        assert int_x == x, \
            f"Can't safely cast a non-integer value ({x}) to integer"
    return int_x

but I wonder if there is a better or more Pythonic way?

3 Answers

If I understand you correctly, you only want to cast something if it's a whole number. If that's the case, you could first cast it to a float and then check with float.is_integer() function if it's an integer.

Here are the examples with values of the question.

>>> float('3.0').is_integer()
True
>>> float('3.000').is_integer()
True
>>> float('3.1').is_integer()
False
>>> float('4.2').is_integer()
False

You could convert to float and modulus the data with 1 to check if you want to keep it a float

val = float(src)
val = int(val) if not val%1 else val

Edit: is_integer() is just doing the below for you, but with a bunch of conditions and flags attached before it gets to this line.

o = (floor(x) == x) ? Py_True : Py_False;

If you want things that look like integers, but aren't really integer values, as in (1.03-0.42)*100, then you need to test to see how "near" an integer a value is, and accept anything close. How close you accept as "integer" will depend on your exact use case:

import sys
tests = [
    42,
    '1.00',
    3.2,
    (1.03-0.42)*100,
]

for x in tests:
    is_close_to_int = abs(int(float(x))-float(x))<0.00000000001
    print(f"{x}: {float(x).is_integer()} {is_close_to_int}")

This outputs:

42: True True
1.00: True True
3.2: False False
61.00000000000001: False True

Showing that for many cases float's is_integer helper will do the right thing, but for some edge cases, a person might expect different results.

Related