None

Float to Integer Conversion In Python

A quick refresher on converting floating point numbers to integers and the implications of each method.

Background

In Python, and really programming generally, we encounter scenarios where a numerical value stored as a floating point number ("float") needs to be converted to an integer. This might sound easy enough. In Python, we simply pass a float to the int() constructor and receive back an integer object. But this is just one approach among others with each having its own implications.

The primary ways to convert floats to integers are:

  • truncation
  • flooring
  • ceiling
  • rounding

Whenever you move from a float to integer you're by definition losing precision. The differences between the methods listed above deal (primarily) with how data is lost.

Let's take a brief look at each.

 

Examples presented assume Python 3.11.12.

Truncation

The simplest form of converting a float to an integer is truncation and is implemented with the Python function from the math library trunc(). Truncating means to shorten. When we truncate a number, we're simply dropping the decimal. It doesn't matter what data exists to the right of the decimal point, all is dropped and what remains to the left is unaffected. For example, both 1.000001 and 1.999999 truncate to 1.

from math import trunc

trunc(1.000001), trunc(1.999999)
Out[2]: (1, 1)

This output may be surprising because these numbers are quite different, with one being nearly double in magnitude from the other. However, whether or not this behavior is desirable is beside the point. This is simply how truncation works. This is also how int() is implemented.

int(1.000001), int(1.999999)
Out[3]: (1, 1)

Although int() gives us a bit more than trunc() alone, such as type coercion.

 

Common Pitfall

A common error I see among junior developers is the expectation that int() will round a float based on 'away-from-zero' or 'round-up' rounding methods since that's what we most often encounter in daily life. This is not the case for int(). The function simply truncates a float.

Flooring

The next method we'll look at is flooring. The process of flooring a float is to return the largest integer less than or equal to the input float. This is implemented in Python with the floor() function found in the math library. Let's see how our example numbers 1.000001 and 1.999999 are treated by this function

from math import floor

floor(1.000001), floor(1.999999)
Out[8]: (1, 1)

Well that sort of looks like truncation. And for all positive values that's correct. However, things get a little tricky for negative values. Remember, flooring a float returns the largest integer that's smaller than the input float. If we swap the signs on our example numbers, we'll see this in action.

from math import floor

floor(-1.000001), floor(-1.999999)
Out[10]: (-2, -2)

Here we now have -2 because -2 is the next integer that's smaller than the input floats. On the number line, -2 is a smaller value than -1. This is an important distinction between floor() and trunc().

Ceiling

Next up we have ceiling. Taking the ceiling of a float value is basically the opposite implementation of flooring a float. Instead of returning the largest integer that's smaller than our input float, we're looking for the smallest integer that's greater than or equal to the input float. This is implemented with the ceil() function found in the math library. 

from math import ceil

ceil(1.000001), ceil(1.999999)
Out[12]: (2, 2)

Similarly to floor(), we'll see that taking the ceiling of a negative value will return a smaller absolute value since smaller absolute values are less negative and therefore larger values when the sign is considered.

from math import ceil

ceil(-1.000001), ceil(-1.999999)
Out[14]: (-1, -1)

Again, try to visualize a number line if you find this confusing.

Rounding

The last method we'll look at is the rounding method of dealing with converting floats to integers. However, we need to differentiate where we're rounding to since unlike our previous examples that all returned integers, rounding can occur anywhere past the decimal and even before the decimal. For purposes of our discussion, we'll be rounding to zero (0) places after the decimal.

To implement rounding in Python, we don't need to rely on the math library. The round() function is available to us directly as a built-in function. Let's take a look at how our example numbers will be modified by this function.

round(1.000001,0), round(1.999999,0)
Out[15]: (1.0, 2.0)

These outputs are likely closer to what we would intuitively expect. However, the resulting values are still floats and we're looking for integers. One approach would be to modify this to explicitly convert the resulting value to an integer with int().

int(round(1.000001,0)), int(round(1.999999,0))
Out[16]: (1, 2)

Which would be roughly equivalent to using math.trunc().

from math import trunc

trunc(round(1.000001,0)), trunc(round(1.999999,0))
Out[18]: (1, 2)

Because the value has already been rounded and no significant digits exist to the right of the decimal point, post-rounding accuracy is unaffected. However, Python allows us to omit the rounding precision entirely and instead return an integer directly.

 

The return value is an integer if ndigits is omitted or None. Otherwise, the return value has the same type as number.

round(1.000001), round(1.999999)
Out[19]: (1, 2)

Excellent, we now have the value closer to the larger integer rounding up and the value closer to the smaller integer rounding down. But what about tie breakers?

round(1.4), round(1.5), round(1.6)
Out[21]: (1, 2, 2)

Great, we're resolving tie breakers just as we'd expect with 0.5 resulting in a "round-up." However, this might not always hold.

round(2.4), round(2.5), round(2.6)
Out[22]: (2, 2, 3)

Here, we see that 0.5 actually resulted in down-rounding to the smaller integer. What's the deal?

This is referred to as banker's rounding. This form of rounding is where the tie breaker is given to the closest even choice.

Imagine the following digits on a number line: (1, 1.5, 2). The closest even choice to 1.5 is 2. For our second example (2, 2.5, 3), the closest even choice is also 2. This is why we see the first example rounding up to 2 and the second example rounding down to 2.

The reason for this is to reduce biases when rounding occurs in large volume. This might not matter to us if we're only performing the operation a couple of times. But imagine now we're rounding billions of times. Giving the tie breaker consistently to one side will result in the cumulative sum being biased in that direction. By allowing the tie breaker to go to either side in a predictable fashion, we're able to reduce the bias in the number at scale.

Additional Considerations

When working with floating point numbers, we need to remember that certain real numbers cannot be perfectly represented as a float. This will affect rounding and integer conversion largely because number representations may lose precision when stored. This is because while we mostly work with base 10 number representations, our machines are storing the numbers as base-2 (binary) representations.

For example, consider 0.10.

a = .1

a
Out[30]: 0.1

Looks like we still have one-tenth. Now let's go out 10 places past the decimal.

format(a, '.10f')
Out[31]: '0.1000000000'

Still one-tenth. So far, so good. Let's go further.

format(a, '.25f')
Out[32]: '0.1000000000000000055511151'

And there's the problem. We're not storing exactly 0.1. Instead, the system is storing an approximation of 0.1 because there isn't an exact base-2 representation of this number.

This isn't the end of the world and it's not a bug. It's simply the reality of how base-2 number representations are stored and we need to keep this in mind while we're living in a base-10 world.

But to be fair, this isn't a problem specific to base-2, we also have issues with accurately recording certain numbers with base-10 representations too. Consider the fraction 1/3. No matter how many 3's we add past the decimal (0.3333 and so on infinitely), any base-10 decimal representation of the fraction 1/3 will always be just an approximation.

Final Thoughts

There are a handful of ways to convert floating point numbers to integers. We discussed four in particular: truncation, flooring, ceiling, and rounding. Still, this isn't an exhaustive list. The commonly encountered "away-from-zero" rounding wasn't discussed and doesn't have a native function in Python. You're going to have to implement your own function if this is the functionality you desire.

Whatever you decide to go with will likely depend on your particular use case. This discussion is only to remind you how each particular approach affects how precision beyond the decimal point is ultimately lost so that you can choose the approach that's best for you. 

Details
Published
August 25, 2025
Tags
Next
April 16, 2024

Considerations for Updating Time Zones in Django

Additional steps to consider when configuring time zones in your Django project for optimal user experience.