186

Can someone explain this (straight from the docs- emphasis mine):

math.ceil(x) Return the ceiling of x as a float, the smallest integer value greater than or equal to x.

math.floor(x) Return the floor of x as a float, the largest integer value less than or equal to x.

Why would .ceil and .floor return floats when they are by definition supposed to calculate integers?


EDIT:

Well this got some very good arguments as to why they should return floats, and I was just getting used to the idea, when @jcollado pointed out that they in fact do return ints in Python 3...

Community
  • 1
  • 1
Yarin
  • 173,523
  • 149
  • 402
  • 512
  • 1
    My guess would be that it's because x is a float, not an integer, but since I don't know or use Python, I'll let someone else answer more definitively. :) – Adam V Dec 20 '11 at 22:26
  • 7
    @Adam- but the whole point of ceil/floor operations is to round floats to integers! – Yarin Dec 20 '11 at 22:40
  • 2
    This also irked me the first time I came across it, because it just seems wrong. At least, it's not too hard to use `int(floor(n))`. – wim Dec 21 '11 at 05:14
  • 2
    Ironically (as floats were used to prevent overflows), the value returned by floor/ceil is meaningless in the low digits because of the float representation, well before a 64 bits int would overflow. [This wasn't true in the old days of 32 bits.] –  Apr 18 '16 at 06:15

9 Answers9

115

As pointed out by other answers, in python they return floats probably because of historical reasons to prevent overflow problems. However, they return integers in python 3.

>>> import math
>>> type(math.floor(3.1))
<class 'int'>
>>> type(math.ceil(3.1))
<class 'int'>

You can find more information in PEP 3141.

jcollado
  • 39,419
  • 8
  • 102
  • 133
  • @jcollado- Where do you see that they return integers in P3? – Yarin Dec 20 '11 at 22:39
  • 5
    @Yarin I just typed the commands above. Also if you try with `float("inf")` or `float("nan")`, you'll get an `OverflowError` exception. – jcollado Dec 20 '11 at 22:40
  • @jcollado- Wow interesting- I was just getting used to the returning as float concept... – Yarin Dec 20 '11 at 22:48
  • 11
    For completeness, Python's `numpy.floor` and `ceil` return floats () – Neil G Dec 21 '11 at 04:36
  • 1
    @jcollado: `float("inf")` does not produce an exception in Python 2.7 or 3 – endolith Oct 14 '13 at 14:57
  • 1
    @endolith You're right, I've checked that and it doesn't happen anymore. Probably that's something that has been changed since Dec 2011. – jcollado Oct 14 '13 at 15:28
  • 1
    Is there a way to make python 2.x return integer in Math.ceil() ( (e.g. import a __future__ some_module) ). I would like to avoid doing int(Math.ceil(..)) and remember to remove at the time of upgrading to 3.x. Thanks. – user1055761 Nov 11 '17 at 19:25
106

The range of floating point numbers usually exceeds the range of integers. By returning a floating point value, the functions can return a sensible value for input values that lie outside the representable range of integers.

Consider: If floor() returned an integer, what should floor(1.0e30) return?

Now, while Python's integers are now arbitrary precision, it wasn't always this way. The standard library functions are thin wrappers around the equivalent C library functions.

Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285
  • 14
    And even though Python integers are now arbitrary precision, there are still floats whose floor and ceiling can't be represented by integers. Try `floor(float("inf"))` or `ceil(float("nan"))`. – Michael Hoffman Dec 20 '11 at 22:33
  • 4
    @Michael- Apparently in P3 you will now get OverflowExceptions if you try that. See [jcollado](http://stackoverflow.com/a/8582845/165673)'s answer – Yarin Dec 20 '11 at 22:57
  • 5
    Er, it should return a 'long' type (a.k.a. 'bigint'), shouldn't it? Seems like an obvious answer to me, but now I feel like I'm being naive somehow. – koschei Dec 21 '11 at 00:13
  • 4
    @koschei: It does in Python 3.x, see jcollado's answer. – Greg Hewgill Dec 21 '11 at 00:14
  • 1
    See also [a comment to another answer](http://stackoverflow.com/questions/8582741/why-do-pythons-math-ceil-and-math-floor-operations-return-floats-instead-of#comment52705685_8582849) why this makes sense even for numbers that are in range. – ivan_pozdeev Oct 30 '16 at 04:47
20

Because python's math library is a thin wrapper around the C math library which returns floats.

Charles
  • 1,820
  • 13
  • 16
  • The C math library supports arbitrary precision integers? Because that's what other python math functions return. – endolith Oct 14 '13 at 14:59
18

The source of your confusion is evident in your comment:

The whole point of ceil/floor operations is to convert floats to integers!

The point of the ceil and floor operations is to round floating-point data to integral values. Not to do a type conversion. Users who need to get integer values can do an explicit conversion following the operation.

Note that it would not be possible to implement a round to integral value as trivially if all you had available were a ceil or float operation that returned an integer. You would need to first check that the input is within the representable integer range, then call the function; you would need to handle NaN and infinities in a separate code path.

Additionally, you must have versions of ceil and floor which return floating-point numbers if you want to conform to IEEE 754.

Stephen Canon
  • 103,815
  • 19
  • 183
  • 269
  • Stephen- I rewrote my comment- I had meant round, not convert. But that wasn't the source of my confusion- rather it was that I wasn't recognizing the range disparity. – Yarin Dec 20 '11 at 22:41
  • 1
    Sadly, I am out of votes today. This is the correct answer, much more so than any limitation of representation. – Marcin Feb 07 '12 at 16:39
  • 15
    In my years of programming, I don't recall ever encountering a situation where I *wanted* the result of floor/ceil to be a float instead of an integer. The fact that python3 *does* return integers shows that this is in fact the more useful thing to do. I don't buy the "the point of..." claim; it seems that you're defining the point based on what it does, rather than what a programmer might want. – ShreevatsaR Aug 18 '13 at 17:25
  • 2
    @ShreevatsaR I had that situation a couple of times (however, mainly outside of a Python context). The times I remember are when working with Mandelbrot sets. Sometimes you need to make an integral value, but then apply some floating point operation to the value immediately afterwards (say, scaling it by 0.5). Then it's much more efficient to keep the floored float and apply a floating point operation to it than first converting it to an int and then immediately converting it back to float. – blubberdiblub Sep 06 '15 at 03:55
5

Before Python 2.4, an integer couldn't hold the full range of truncated real numbers.

http://docs.python.org/whatsnew/2.4.html#pep-237-unifying-long-integers-and-integers

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • 2
    It can't hold the "full range of truncated real numbers" now, either, because that is obviously an infinite set and would therefore require an infinite amount of memory. It can hold the range of truncated _floats_, which is but a small subset of ℝ. – leftaroundabout Dec 21 '11 at 05:08
  • 6
    @leftaroundabout - picky picky! You knew what I meant. – Mark Ransom Dec 21 '11 at 05:15
4

This is a very interesting question! As a float requires some bits to store the exponent (=bits_for_exponent) any floating point number greater than 2**(float_size - bits_for_exponent) will always be an integral value! At the other extreme a float with a negative exponent will give one of 1, 0 or -1. This makes the discussion of integer range versus float range moot because these functions will simply return the original number whenever the number is outside the range of the integer type. The python functions are wrappers of the C function and so this is really a deficiency of the C functions where they should have returned an integer and forced the programer to do the range/NaN/Inf check before calling ceil/floor.

Thus the logical answer is the only time these functions are useful they would return a value within integer range and so the fact they return a float is a mistake and you are very smart for realizing this!

4

Because the range for floats is greater than that of integers -- returning an integer could overflow

kyle
  • 65
  • 1
  • 8
    Integers don't overflow in Python; its integers convert to bigints when they become too large. Open up a Python interpreter and type "2 ** 500", and you'll see that you get an object you can treat in every way like an int. – koschei Dec 21 '11 at 00:14
  • @koschei: Even bigints overflow when trying to represent infinity. – Stephen Canon Dec 21 '11 at 19:10
1

This totally caught me off guard recently. This is because I've programmed in C since the 1970's and I'm only now learning the fine details of Python. Like this curious behavior of math.floor().

The math library of Python is how you access the C standard math library. And the C standard math library is a collection of floating point numerical functions, like sin(), and cos(), sqrt(). The floor() function in the context of numerical calculations has ALWAYS returned a float. For 50 YEARS now. It's part of the standards for numerical computation. For those of us familiar with the math library of C, we don't understand it to be just "math functions". We understand it to be a collection of floating-point algorithms. It would be better named something like NFPAL - Numerical Floating Point Algorithms Libary. :)

Those of us that understand the history instantly see the python math module as just a wrapper for the long-established C floating-point library. So we expect without a second thought, that math.floor() is the same function as the C standard library floor() which takes a float argument and returns a float value.

The use of floor() as a numerical math concept goes back to 1798 per the Wikipedia page on the subject: https://en.wikipedia.org/wiki/Floor_and_ceiling_functions#Notation

It never has been a computer science covert floating-point to integer storage format function even though logically it's a similar concept.

The floor() function in this context has always been a floating-point numerical calculation as all(most) the functions in the math library. Floating-point goes beyond what integers can do. They include the special values of +inf, -inf, and Nan (not a number) which are all well defined as to how they propagate through floating-point numerical calculations. Floor() has always CORRECTLY preserved values like Nan and +inf and -inf in numerical calculations. If Floor returns an int, it totally breaks the entire concept of what the numerical floor() function was meant to do. math.floor(float("nan")) must return "nan" if it is to be a true floating-point numerical floor() function.

When I recently saw a Python education video telling us to use:

i = math.floor(12.34/3)

to get an integer I laughed to myself at how clueless the instructor was. But before writing a snarkish comment, I did some testing and to my shock, I found the numerical algorithms library in Python was returning an int. And even stranger, what I thought was the obvious answer to getting an int from a divide, was to use:

i = 12.34 // 3

Why not use the built-in integer divide to get the integer you are looking for! From my C background, it was the obvious right answer. But low and behold, integer divide in Python returns a FLOAT in this case! Wow! What a strange upside-down world Python can be.

A better answer in Python is that if you really NEED an int type, you should just be explicit and ask for int in python:

i = int(12.34/3)

Keeping in mind however that floor() rounds towards negative infinity and int() rounds towards zero so they give different answers for negative numbers. So if negative values are possible, you must use the function that gives the results you need for your application.

Python however is a different beast for good reasons. It's trying to address a different problem set than C. The static typing of Python is great for fast prototyping and development, but it can create some very complex and hard to find bugs when code that was tested with one type of objects, like floats, fails in subtle and hard to find ways when passed an int argument. And because of this, a lot of interesting choices were made for Python that put the need to minimize surprise errors above other historic norms.

Changing the divide to always return a float (or some form of non int) was a move in the right direction for this. And in this same light, it's logical to make // be a floor(a/b) function, and not an "int divide".

Making float divide by zero a fatal error instead of returning float("inf") is likewise wise because, in MOST python code, a divide by zero is not a numerical calculation but a programming bug where the math is wrong or there is an off by one error. It's more important for average Python code to catch that bug when it happens, instead of propagating a hidden error in the form of an "inf" which causes a blow-up miles away from the actual bug.

And as long as the rest of the language is doing a good job of casting ints to floats when needed, such as in divide, or math.sqrt(), it's logical to have math.floor() return an int, because if it is needed as a float later, it will be converted correctly back to a float. And if the programmer needed an int, well then the function gave them what they needed. math.floor(a/b) and a//b should act the same way, but the fact that they don't I guess is just a matter of history not yet adjusted for consistency. And maybe too hard to "fix" due to backward compatibility issues. And maybe not that important???

In Python, if you want to write hard-core numerical algorithms, the correct answer is to use NumPy and SciPy, not the built-in Python math module.

import numpy as np

nan = np.float64(0.0) / 0.0    # gives a warning and returns float64 nan

nan = np.floor(nan)            # returns float64 nan

Python is different, for good reasons, and it takes a bit of time to understand it. And we can see in this case, the OP, who didn't understand the history of the numerical floor() function, needed and expected it to return an int from their thinking about mathematical integers and reals. Now Python is doing what our mathematical (vs computer science) training implies. Which makes it more likely to do what a beginner expects it to do while still covering all the more complex needs of advanced numerical algorithms with NumPy and SciPy. I'm constantly impressed with how Python has evolved, even if at times I'm totally caught off guard.

Curt Welch
  • 339
  • 3
  • 5
1

Maybe because other languages do this as well, so it is generally-accepted behavior. (For good reasons, as shown in the other answers)

Almo
  • 15,538
  • 13
  • 67
  • 95