27

In Python is there a way to turn 1.0 into a integer 1 while the same function ignores 1.5 and leaves it as a float?

Right now, int() will turn 1.0 into 1 but it will also round 1.5 down to 1, which is not what I want.

U13-Forward
  • 69,221
  • 14
  • 89
  • 114
Raymond Shen
  • 628
  • 1
  • 6
  • 7

9 Answers9

80

Continuing from the comments above:

Using is_integer():

Example from the docs:

>>> (1.5).is_integer()
False
>>> (1.0).is_integer()
True
>>> (1.4142135623730951).is_integer()
False
>>> (-2.0).is_integer()
True
>>> (3.2).is_integer()
False

INPUT:

s = [1.5, 1.0, 2.5, 3.54, 1.0, 4.4, 2.0]

Hence:

print([int(x) if x.is_integer() else x for x in s])

Wrapped in a function:

def func(s):
    return [int(x) if x.is_integer() else x for x in s]

print(func(s))

If you do not want any import:

def func(s):
    return [int(x) if x == int(x) else x for x in s]

print(func(s))

Using map() with lambda function and the iter s:

print(list(map(lambda x: int(x) if x.is_integer() else x, s)))

OR

print(list(map(lambda x: int(x) if int(x) == x else x, s)))

OUTPUT:

[1.5, 1, 2.5, 3.54, 1, 4.4, 2]
DirtyBit
  • 16,613
  • 4
  • 34
  • 55
  • 15
    If anyone is curious about about the limits of is_integer() and how far away from 1.0 a floating point number can be and still be considered an integer, I wrote a short script to find the limit. It's a very small number, much more precision than I have ever needed for anything. (1.0 + 1.1102230246251566636831481088739149080825883254353483864385054857848444953560829162597656251e-16).is_integer() returns False, but (1.0 + 1.1102230246251566636831481088739149080825883254353483864385054857848444953560829162597656250e-16).is_integer() returns True. – Josh Davis Apr 04 '19 at 18:33
  • 25
    @JoshDavis That's just about rounding to `1.0` vs. `1.0 + sys.float_info.epsilon`. – LegionMammal978 Apr 04 '19 at 19:55
  • All solutions I see here, based on ```is_integer```, will fail miserably if they receive an integer value, because integers doesn't have that method. To be safe, something like this will help: ```to_int = lambda x: int(x) if float(x).is_integer() else x``` – accdias Apr 05 '19 at 02:41
  • 6
    @accdias: That won't always work either, since `float` has representational limits while `int` does not. Passing `x = 1 << 1024` will cause your code to die with an `OverflowError`. So now you've got additional checks for safety, or exception handling. – ShadowRanger Apr 05 '19 at 03:09
  • @ShadowRanger, that is true but I'm pretty sure most of people will be never reaching the ```1 << 1024``` limit, if ever. On the other hand, I think it is much more feasible to see someone trying to do ```2 .is_integer()``` than ```float(1<<1024)```. Good info anyway. – accdias Apr 05 '19 at 03:57
  • You could check if the second element of `.as_integer_ratio()` is 1, as this is also defined on ints (at least on python3.8+). – L3viathan Apr 05 '19 at 17:14
32

In case your goal is to convert numbers to a concise string, you could simply use '%g' ("General Format") for formatting:

>>> '%g' % 1.0
'1'
>>> '%g' % 1
'1'
>>> '%g' % 1.5
'1.5'
>>> '%g' % 0.3
'0.3'
>>> '%g' % 0.9999999999
'1'

You can specify the desired accuracy:

>>> '%.15g' % 0.999999999999999
'0.999999999999999'
>>> '%.2g' % 0.999
'1'
Eric Duminil
  • 52,989
  • 9
  • 71
  • 124
  • This is great but I had a query yesterday that for some reason I couldn't ask, this would turn `0.9` to `1` but considering one only wants `1.0` to `1` or `2.0` to `2` and not `0.9` to `1` or `1.9` to `2`. Do we have a workabout in that case? cheers! – DirtyBit Apr 05 '19 at 11:13
  • @DirtyBit: Sorry, I really don't understand the question. `'%g' % 0.9` is `'0.9'` and `'%g' % 2.0` is `'2'`. Floats are displayed as ints only if they're really close to an int, e.g. `0.9999995`. But not `0.9` or `0.99` – Eric Duminil Apr 05 '19 at 11:20
  • exactly: `print('%g' % 0.9999999999) # 1` but let's say the req. was to only have `1` if it was `1.0` and not `0.9999999999`? – DirtyBit Apr 05 '19 at 11:23
  • 2
    @DirtyBit: You can specify the desired accuracy : `'%.15g' % 0.999999999999999` is `'0.999999999999999'` and `'%.2g' % 0.999`is `'1'`. From this [article](https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html) : *Rounding error is the characteristic feature of floating-point computation*. – Eric Duminil Apr 05 '19 at 11:35
  • that is fascinating but still gives a little sense of hardcoding. no? Nevertheless, you have my upvote! – DirtyBit Apr 05 '19 at 14:57
  • @DirtyBit: I'm not sure what you mean with hardcoding. A float cannot be an integer, it can only seem to be close enough to an int : `1.0000000000000001.is_integer() #=> True` There's always an epsilon involved, even if it is hidden. – Eric Duminil Apr 05 '19 at 15:03
  • 2
    Best answer since this is the only valid use case for what the OP wants anyway. – jpmc26 Apr 07 '19 at 08:59
  • @jpmc26: Thanks. That's what I thought too : there's no need to selectively convert floats and ints otherwise. – Eric Duminil Apr 07 '19 at 09:04
14

float.is_integer is a method on floats that returns whether or not the float represents an integer.

You can just use this function I made called to_int, that uses is_integer to check whether it represents an integer (e.g. 1.0) or not (e.g. 1.5).

If it represents an integer, return int(a), otherwise just return it's original value.

As you see, I am not using elif or else because return returns only once:

def to_int(a):
   if a.is_integer():
      return int(a)
   return a

print(to_int(1.5))
print(to_int(1.0))

Output:

1.5
1
U13-Forward
  • 69,221
  • 14
  • 89
  • 114
5

Python floats are approximations, so something that prints as 1.0 is not necessarily exactly 1.0. If you want to see if something is approximately an integer, use a sufficiently small epsilon value.

EPSILON = 0.0001 # Make this smaller or larger depending on desired accuracy

def func(x):
  if abs(x - round(x)) < EPSILON:
    return round(x)
  else:
    return x

In general, if you're checking whether a float is == to something, that tends to be a code smell, as floating point values are inherently approximate. It's more appropriate in general to check whether a float is near something, within some epsilon range.

Silvio Mayolo
  • 62,821
  • 6
  • 74
  • 116
  • 6
    While most floating point values are approximations, there are many integers that can be represented exactly. A 64-bit IEEE 754 floating point number (as used in Python and other languages) can represent exactly any signed integer that fits in 53 bits or less. Such integers can be converted 1 to 1 between a 64-bit float and 64-bit integer assuming the integer value could be represented with as little as 53 bits. Addition, subtraction, and multiplication that stays within 53-bits of integer space should yield identical results per IEEE 754 spec. – penguin359 Apr 04 '19 at 18:04
  • 1
    @penguin359 While I agree that integers *can* be represented exactly in floating point, if you're in the situation that OP is in, you need to ask yourself how close to an integer is "close enough" for your purpose. Is `1.0 + 1e-16` "close enough"? Is `1.0 + 2e-16`? It's an important question, as IEEE 754-based "is integer" methods will treat them differently, and you may or may not want to consider `1.0 + 2e-16` "not an integer" while saying `1.0 + 1e-16` is one. -- Having an explicit epsilon (versus the implicit one from IEEE 754) makes your desires clearer. – R.M. Apr 04 '19 at 19:30
  • A comment seeming best at this answer: Setting an epsilon works as in the example provided works fine. Alternatively, especially if dealing with cents (currencies in general), I found it more useful to change from Python's default of binary representation to decimal one, as provided by module decimal (https://docs.python.org/3.7/library/decimal.html). The difference, for example in a loop iterating from -1 to +1 in increments of 0.1 is either passing very close to 0, or right at 0.0 -- without need for an epsilon set (how would you deal with -1.3877787807814457e-16 currency units?). – Buttonwood Apr 04 '19 at 19:39
  • 1
    "so something that prints as 1.0 is not necessarily exactly 1.0" That depends on the version of python. In modern python3 (IIRC since python 3.2) a float that prints as 1.0 is exactly 1.0. In older versions of python it may not be. Try "print (float(numpy.nextafter(1.0,2)))" to see what happens on your version. – plugwash Apr 05 '19 at 13:36
1

for list of numbers:

def get_int_if_possible(list_of_numbers):
    return [int(x) if x == int(x) else x for x in list_of_numbers]

for one number:

def get_int_if_possible(number):
    return int(number) if number == int(number) else number
Baruch Gans
  • 1,415
  • 1
  • 10
  • 21
-2

A simple thing you could do is use the modulo operator:

if (myFloat % 1 == 0) // Number is an int
else // numer is not an int

EDIT: Python code

if myFloat % 1 == 0:
  # myFloat is an integer.
else:
  # myFloat is NOT an integer
SimonC
  • 1,547
  • 1
  • 19
  • 43
-3

What I used to do in the past in C++ is, lets say you have these variables:

float x = 1.5;
float y = 1.0;

Then you could do something like this:

if(x == (int)x) 
    return 1;
else return 0;

This will return 0 because 1.5 is not equal to 1

if(y == (int)y) 
    return 1;
else return 0;

This will return 1 because 1.0 is equal to 1

Of course your question is about Python and the function is_integer() should work great, I just thought some people might find this useful.

  • 1
    The question was to convert a float to int whenever the float in question has an integer value. AFAIK this is not possible in C++, because you cannot return different values from a function. – M.Herzkamp Apr 04 '19 at 11:49
  • 4
    @M.Herzkamp you could have a `union` though. – Baldrickk Apr 04 '19 at 12:13
  • And also you could set another integer variable and save the float there if the condition above is met – Stefan Kostoski Apr 04 '19 at 12:40
  • 1
    The digression on C++ isn't really relevant. You can do the same thing in Python: `foo = lambda x: int(x) if int(x) == x else x`. (Or in Python 3.8, to avoid the duplicate call to `int`, `lambda x: y if (y:=int(x)) == x else x`. – chepner Apr 04 '19 at 13:11
  • @chepner I tried `foo = lambda x: int(x) if int(x) == x else x` the very moment I wrote the answer above, for some reason it did not work for me in 3.6. Any clue? – DirtyBit Apr 04 '19 at 13:18
  • @DirtyBit None at all, because I don't know what you mean by "did not work". – chepner Apr 04 '19 at 13:21
  • @chepner somthing like `print(list(filter(lambda x: int(x) if x.is_integer() else x, s)))` returns `[1.5, 1.0, 2.5, 3.54, 1.0]`? – DirtyBit Apr 05 '19 at 06:09
  • 1
    `filter` doesn't modify the values in the list; the function is just a predicate that decides whether or not to *select* the original value for its output. Use `map` instead. – chepner Apr 05 '19 at 11:14
  • @chepner, that is what happens when you miss you caffeine shot. Indeed, map it was. Cheers! – DirtyBit Apr 05 '19 at 16:20
-3
def your_function(i):
  if i.is_integer():
    return int(i)
  else:
    return float(i)
xcrafter_40
  • 107
  • 1
  • 10
  • While this may answer the question, it would be very helpful if you could add on what is happening in there and how does it solve the problem, in order to increase the lifetime of your answer and to attract the users looking for the similar solution. – DirtyBit Apr 10 '19 at 10:33
  • strings and floats have a function called is_integer, which returns true if it's a number, and false if anything else (including floats). So, if it's an int, return it as an int. Anything else (it's a float) and return it as a float. – xcrafter_40 Apr 11 '19 at 01:01
  • @ xcrafter_40 not as a comment, you may edit your answer to add the explanation. :) – DirtyBit Apr 11 '19 at 06:30
  • what was the other downvote for? – xcrafter_40 Apr 26 '19 at 05:16
-3

divmod alternative:

def f(x):
    return x if divmod(x, 1)[1] else int(x)
sardok
  • 1,086
  • 1
  • 10
  • 19