Lua claims that floating point numbers can represent integer numbers just as exactly as integer types can, and I'm inclined to agree. There's no imprecise representation of a fractional numeric part to deal with. Whether you store an integer in an integer type, or store it in the mantissa of a floating point number, the result is the same: that integer can be represented exactly, as long as you don't exceed the number of bits in the mantissa, + 1 bit in the exponent.
Of course, if you try to store an actual floating-point number (e.g. 12.345) in a floating point representation, all bets are off, so your program has to be clear that the number is really a genuine integer that doesn't overflow the mantissa, in order to treat it like an actual integer (i.e. with respect to comparing equality).
If you need more integer precision than that, you can always employ an arbitrary-precision library.
Further Reading
What is the maximum value of a number in Lua?
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.
Best Answer
Consider this hypothetical.
You have two data types. One is a floating point number with 32 bits of precision that is base 3 (represented by the digits 0, 1, and 2). The other is a floating point number with 64 bits of precision that is base 4.
Given this code:
The comparison will return
true
if base 3 floats are used, andfalse
if base 4 floats are used, even though the base 4 float has a much higher precision.Why is this? Because the numbers 3, 6, and 9 can be represented exactly in the base 3 float, but can only be approximated in the base 4 float, no matter how many digits of precision there are.
This is why floating point numbers should never be compared using
==
. They should always be compared using a range of allowable error.