While I understand that the difference between integral and long values is blurred in Python, the difference between floats and integral values is not.

Therefore, I'm having a difficult time understanding why math.ceil and math.floor return floating-point values when the only possible outcome is integral, whole numbers.

For example, if I do the following:

math.ceil(7.5)

I'd expect to get an integer with the value 8, not a floating-point number with the value 8.0. The same goes for math.floor.

Why does Python in this case use floating-point numbers where integers make more sense based on the output?

  • 1
    Because your working domain is floating point numbers, not integers. If I call a Round function on a floating point number, I expect to get back a floating point number. You don't suddenly switch to integers if you call Round with a "digits after the decimal point" value of zero; it's not a special case. – Robert Harvey Aug 20 '15 at 0:27
  • To put it another way, forcing me to use a cast to work in floating point numbers again, or worse, making that cast conditional on whether or not there are nonzero digits after the decimal point, would be an enormous pain in the ass. – Robert Harvey Aug 20 '15 at 0:29
  • Ah, that makes sense. Can you reply in an answer with an example that clearly demonstrates your valid point? – Naftuli Kay Aug 20 '15 at 0:31
  • 5
    Actually in Python 3 it does return an integer. – user7043 Aug 20 '15 at 7:16
  • In C#, the similar Math.Ceiling(double) also returns a double. – Arseni Mourzenko Aug 31 '15 at 19:53

Short answer

It was a bug. Well, not exactly a bug, but the behavior was changed based on a proposal for Python 3. Now, ceil and floor return integers (see also delnan's comment). Some details are here: http://www.afpy.org/doc/python/2.7/whatsnew/2.6.html

Why Python originally returned floats

This question has some nice answers about the behaviour before Python 3. Since the mathematical operators where wrappers around the C mathematical operators, it made sense to follow the convention of that language. Note that in C, the ceil function takes and returns a double. This makes sense because not all floats can be represented by integers (for values with a big exponent, there is no direct representation with integers).

Python was historically not explicitely designed to formally conform to some of the properties of mathematical operations (that would not happen by accident). Guido Von Rossum has acknowledged some early design mistakes and explained the rationale behind the types used in Python, notably why he preferred C types instead of reusing the ones in ABC. See for example:

The language is supposed to evolve, though, and people tried to incorporate numeric type systems from other languages. For example, Reworking Python's Numeric Model and A Type Hierarchy for Numbers.

Why it should be an integer

The fact that integer 8 is also a real number does mean that we should return a floating point value after doing floor(8.2), exactly because we would not return a complex value with a zero imaginary part (8 is a complex number too).

This has to do with the mathematical definitions of the operations, not the possible machine representations of values: floor and ceiling mathematical functions are defined to return integers, whereas multiplication is a ring where we expect the product of x and y from set A to belong to set A too. Arguably, it would be surprising if 8.2 * 10 returned the integer 82 and not a floating point; similarly the are no good reasons for floor(8.2) to return 8.0 if we want to be conform to the mathematical meaning.

By the way, I disagree with some parts of Robert Harvey's answer.

  • There are legitimate uses to return a value of a different type depending on an input parameter, especially with mathematical operations.

  • I don't think the return type should be based on a presupposed common usage of the value and I don't see how convenient it would be. And if it was relevant, I'd probably expect to be given an integer: I generally do not combine the result of floor with a floating point.

Inconvenience of Python 3

Using the operations from C in Python could be seen as a leaky abstraction of mathematical operations, whereas Python generally tries to provide a high-level view of data-structures and functions. It can be argued that people programming in Python expect operations that just work (e.g. arbitrary precision integers) and prefer to avoid dealing with numeric types at the level of C (e.g. undefined behaviour of overflow for unsigned signed integers). That's why PEP-3141 was a sensible proposition.

However, with the resulting abstraction, there might be some cases where performance might degrade, especially if we want to take the ceiling or floor of big floats without converting them to big integers (see comment from Mark Dickinson). Some may argue that this is not a big deal if a conversion occurs because it does not impact the overall performance of your program (and this is probably true, in most cases). But unfortunately, the problem here is that the programmer cannot choose which behaviour suits the most her needs. Some languages define more expressive functions: for example Common Lisp provides fflor and fceiling, which return floating-point values. It would be preferable if Python could provide fceil too. Alternatively, a sufficiently smart compiler could detect float(math.ceil(x)) and do the right thing.

  • 5
    In C you obviously need to return a double, since it has a much larger range than integers. I'd consider turning a 64 bit double into a 1000 bit integer by rounding slightly unintuitive even in a language with arbitrarily sized integers. It's be even more extreme for 80 bit floats (extended precision) or custom floats which aren't limited to measly 11 bit exponents. And how would you handle special values, like infinities and NaNs? – CodesInChaos Aug 20 '15 at 8:36
  • @CodesInChaos It makes perfect sense in C to return doubles, I have no problem with that. But Python is supposed to be a higher-level language where things can be made differently. – coredump Aug 20 '15 at 8:39
  • Seems more plausible than the accepted answer ... – robert Aug 20 '15 at 9:34
  • @CodesInChaos: Python 3.4's math.ceil throws an OverflowError for infinity, or ValueError for NaN. – dan04 Aug 20 '15 at 23:29
  • 5
    No, it wasn't a bug: it was a deliberate design decision (encoded in PEP 3141) to change the behaviour in Python 3. (And a misguided one, IMO: ceil : float -> float is computationally a simple and fast operation; ceil : float -> int is significantly more complicated and expensive, especially for large inputs. With the change in Python 3 there's no way to spell that simple operation, while in Python 2 it's easy to do int(ceil(x)).) – Mark Dickinson Aug 31 '15 at 16:00

Because 8 is a perfectly good floating point number.

Let's generalize the concept of math.ceil to include a "digits" parameter; that is, you get to choose the number of digits after the decimal point that you want to keep. This isn't as far-fetched as it sounds; the Round function already has this ability.

By this new definition, Math.Ceil(12.755, 2) would return 12.76, which you wouldn't be able to return as an int. The only values that could be returned as int would be those of the form Math.Ceil(x, 0), but it probably doesn't make much sense to have a function that returns a different type based on the value of one of its input parameters.

Anyway, it's more convenient to stay in the floating-point realm for working with these numbers, especially since any subsequent math on the returned numbers is almost certainly going to involve floating point anyway.

  • 2
    A parameter signifying decimal digits while rounding a binary floating point number might not be the best of ideas. If the rounding is for display purposes I'd do it as part of the conversion to string, if it's required for the correctness of the computation, a binary floating point number probably isn't the correct choice of type. – CodesInChaos Aug 20 '15 at 8:32

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.