[Python-Dev] Deprecating float.is_integer()

Tim Peters tim.peters at gmail.com
Wed Mar 21 19:43:04 EDT 2018


Note:  this is a top-posted essay much more about floating-point
philosophy than about details.  Details follow from the philosophy,
and if philosophies don't match the desired details will never match
either.

Understanding floating point requires accepting that they're a funky
subset of rational numbers, augmented with some oddballs (NaNs,
"infinities", minus zero).  At best the reals are a vague inspiration,
and floats have their own terminology serving their actual nature.
Thinking about reals instead is often unhelpful.

For example, it's bog standard terminology to call all IEEE-754 values
that aren't infinities or NaNs "finite".  Which, by no coincidence, is
how Python's math.isfinite() discriminates.  Within the finites -
which are all rational numbers - the distinction between integers and
non-integers is obvious, but only after you're aware of it and give it
some thought.  Which most people aren't and don't - but that's no
reason to prevent the rest of us from getting work done ;-)

This isn't anything new in Python - it's as old as floating-point.
For example, look up C's ancient "modf" function (which breaks a
float/double into its "integer" and "fractional" parts, and treats all
finite floats of sufficiently large magnitude as having fractional
parts of 0.0 - because they in are fact exact integers).

The idea that floats are "just approximations - so all kinds of slop
is acceptable and all kinds of fear inescapable" went out of style
when IEEE-754 was introduced.  That standard codified an alternative
view:  that functions on floats should behave as if their inputs were
_exactly_ correct, and - given that - produce the closest
representable value to the infinitely precise result.  That proved to
be extremely valuable in practice, allowing the development of
shorter, faster, more robust, and more accurate numerical algorithms.

The trend ever since has been to do more & more along those lines,
from trig functions doing argument reduction as if pi were represented
with infinite precision, to adding single-rounding dot product
primitives (all again acting as if all the inputs were exactly
correct).

Since that approach has been highly productive in real life, it's the
one I favor.  Arguments like "no floating point number on the order of
1e306 is sufficiently precise as to be an integer in any meaningful
sense" don't even start to get off the ground in that approach.  Maybe
in 1970 ;-)  You can have no actual idea of whether 1e306 is exactly
right or off by a factor of a million just from staring at it, and
real progress has been made by assuming all values are exactly what
they appear to be, then acting accordingly.  If you want to model that
some values are uncertain, that's fine, but then you need something
like interval arithmetic instead.

>From that fundamental "take floats exactly at face value" view, what
.is_integer() should do for floats is utterly obvious:  there is no
possible argument about whether a given IEEE-754 float is or is not an
integer, provided you're thinking about IEEE-754 floats (and not,
e.g., about mathematical reals), and making even a tiny attempt to
honor the spirit of the IEEE-754 standard.

Whether that's _useful_ to you depends on the application you're
writing at the time.  The advantage of the philosophy is that it often
gives clear guidance about what implementations "should do"
regardless, and following that guidance has repeatedly proved to be a
boon to those writing numerical methods.  And, yes, also a pain in the
ass ;-)

--- nothing new below ---

On Wed, Mar 21, 2018 at 3:49 PM, David Mertz <mertz at gnosis.cx> wrote:
> On Wed, Mar 21, 2018 at 3:02 PM, Tim Peters <tim.peters at gmail.com> wrote:
>>
>> [David Mertz]
>> > I've been using and teaching python for close to 20 years and I never
>> > noticed that x.is_integer() exists until this thread.
>>
>> Except it was impossible to notice across most of those years, because
>> it didn't exist across most of those years ;-)
>
>
> That's probably some of the reason.  I wasn't sure if someone used the time
> machine to stick it back into Python 1.4.
>
>>
>> > On the other hand, `x == int(x)` is genuinely obvious..
>>
>> But a bad approach:  it can raise OverflowError (for infinite x); it
>> can raise ValueError (for x a NaN);
>
>
> These are the CORRECT answers! Infinity neither is nor is not an integer.
> Returning a boolean as an answer is bad behavior; I might argue about
> *which* exception is best, but False is not a good answer to
> `float('inf').is_integer()`.  Infinity is neither in the Reals nor in the
> Integers, but it's just as much the limit of either.
>
> Likewise Not-a-Number isn't any less an integer than it is a real number
> (approximated by a floating point number).  It's NOT a number, which is just
> as much not an integer.
>
>>
>> and can waste relative mountains
>> of time creating huge integers, e.g.,
>
>
> True enough. But it's hard to see where that should matter.  No floating
> point number on the order of 1e306 is sufficiently precise as to be an
> integer in any meaningful sense.  If you are doing number theory with
> integers of that size (or larger is perfectly fine too) the actual test is
> `isinstance(x, int)`.  Using a float is just simply wrong for the task to
> start with, whether or not those bits happen to represent something
> Integral... the only case where you should see this is "measuring/estimating
> something VERY big, very approximately."
>
> For example, this can be true (even without reaching inf):
>
>>>> x.is_integer()
> True
>>>> (math.sqrt(x**2)).is_integer()
> False
>
>> The problem there isn't  how "is it an integer?" is spelled, it's that
>> _any_ way of spelling "is it an integer?" doesn't answer the question
>> they're trying to answer.  They're just plain confused about how
>> floating point works.  The use of `.is_integer()` (however spelled!)
>> isn't the cause of that, it's a symptom.
>
>
> Agreed!
>
> --
> Keeping medicines from the bloodstreams of the sick; food
> from the bellies of the hungry; books from the hands of the
> uneducated; technology from the underdeveloped; and putting
> advocates of freedom in prisons.  Intellectual property is
> to the 21st century what the slave trade was to the 16th.


More information about the Python-Dev mailing list