classification
Title: math.isnan(int) and math.isinf(int) should not raise OverflowError
Type: behavior Stage: resolved
Components: Versions:
process
Status: closed Resolution: wont fix
Dependencies: Superseder:
Assigned To: Nosy List: mark.dickinson, steven.daprano, tim.peters
Priority: normal Keywords:

Created on 2016-09-06 17:04 by steven.daprano, last changed 2016-09-07 18:28 by mark.dickinson. This issue is now closed.

Messages (11)
msg274568 - (view) Author: Steven D'Aprano (steven.daprano) * (Python committer) Date: 2016-09-06 17:04
Currently, math.isnan(n) and math.isinf(n) for n an int may raise OverflowError if n is too big to convert to a float, e.g.:

py> math.isnan(10**10000)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OverflowError: int too large to convert to float


But this conversion is unnecessary. int does not support either INF or NAN, so there is no need to convert the value to float first. If the argument is an int, the result must be False.

The same applies to Fraction.
msg274571 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2016-09-06 17:24
I see this as a documentation issue: the vast majority of math module functions are designed to operate on floats, and if given a non-float input, simply convert that input to a float as a convenience. If we start special-casing math module functions for int, Fraction, and Decimal inputs, the module is going to become much more complicated both in terms of implementation and in terms of cognitive load for the user.
msg274572 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2016-09-06 17:30
Related: #18842
msg274573 - (view) Author: Steven D'Aprano (steven.daprano) * (Python committer) Date: 2016-09-06 17:47
As a convenience for whom? Certainly not the poor user, who thinks that 
math.isnan(x) should return False if the number x is not a NAN. Since 
neither 10**1 nor 10**100000 are NANs, why should one return correctly 
and the other raise a completely spurious OverflowError?

I cannot speak for the implementation, except as a wild guess. It 
shouldn't be hard to do the equivalent of:

if type(x) == int: return False  # Intentionally excluding subclasses.
try:
    y = float(x)
except OverflowError:
    return False
else:
    ... # existing implementation

but since I know nothing about the C implementation maybe I'm completely 
wrong. But as far as the user's cognitive load, I don't think that:

"math.isnan(x) might return the expected result, or it might raise 
OverflowError"

is *simpler* for the user than:

"math.isnan(x) will return the expected result".
msg274578 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2016-09-06 17:59
> It shouldn't be hard to do the equivalent of:

Right, that's not hard at all. But is it what we *want* to do? Why do you single out `int` for special treatment, but not `Fraction` or `Decimal`? How should the implementation handle Fraction objects, and why? How should the implementation handle a Fraction-like object implemented by a user? Why only objects of exact type `int`, but not instances of subclasses? Your suggestion replaces a straightforward mental model (math.isnan converts its input to float, then operates on it, just like almost all other math module functions) with something more complicated.

-1 from me.
msg274580 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2016-09-06 18:00
> As a convenience for whom?

I was referring to the general math module model. Being able to type `sqrt(2)` rather than having to type `sqrt(float(2))` or `sqrt(2.0)` is a convenience.
msg274586 - (view) Author: Tim Peters (tim.peters) * (Python committer) Date: 2016-09-06 18:18
The only sane way to do things "like this" is to allow types to define their own special methods (like `__isnan__()`), in which case the math module defers to such methods when they exist.  For example, this is how `math.ceil(Fraction)` works, by deferring to `Fraction.__ceil__()`.  The math module itself knows nothing else about what `ceil(Fraction)` could possibly mean.  All it knows is "if the type has __ceil__ use that, else convert to float first".

I'm also -1 on adding masses of if/else if/else if/.../else constructs to the math module to build in knowledge of the builtin numeric types.  Do it "right" or not at all.

I'd just be -0 on adding masses of new __isnan__, __isinf__, ..., special methods.  They're just not useful enough often enough.
msg274592 - (view) Author: Steven D'Aprano (steven.daprano) * (Python committer) Date: 2016-09-06 18:34
On Tue, Sep 06, 2016 at 05:59:08PM +0000, Mark Dickinson wrote:
> Why do you single out `int` for special treatment, 

Mostly as a demonstration for what could be done, not necessarily as 
what should be done. Secondly as a potential optimization. Why go to the 
expense of converting something to a float when there's a much 
cheaper(?) test?

> but not `Fraction` or `Decimal`?

They're not built-ins. They would have to be imported first, before you 
can test for their types. That could be costly, and it would rarely be 
necessary.

> How should the implementation handle Fraction objects, and why?

If some hypothetical subclass of Fraction provides a NAN or INF value, 
trust that float(x) of those values will return a float NAN or INF. If 
the conversion overflows, the value isn't a NAN or INF.

> How should the implementation handle a Fraction-like object 
> implemented by a user?

As above.

> Why only objects of exact type `int`, but not instances of subclasses?

Subclass of int might hypothetically implement their own NAN or INF 
values. In which case, trust that MyInt('nan').__float__() will return a 
NAN float as it is supposed to.

> Your suggestion replaces a straightforward 
> mental model (math.isnan converts its input to float, then operates on 
> it, just like almost all other math module functions) with something 
> more complicated.

Not more complicated. An even more simple *model*. The existing model 
for (let's say isnan):

"Convert the number x to a float, which may Overflow, then return 
whether the float is a NAN."

Versus the even simpler model:

"Return whether the number x is a NAN."

(Which of course hides a more complex *implementation*.)

Versus the practice of pushing the complexity onto the users:

"Oh gods, what sort of number is my x? If I pass it to math.isnan, will 
it blow up? Better wrap it in a try...except ValueError just in case. 
[Later] Ah, dammit, I meant OverflowError! Oh no, is there an 
UnderflowError for Fractions?"

I guess the argument boils down to whether we want to prioritise 
simplicity or usefulness in the math module.
msg274596 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2016-09-06 18:43
[Steven]
> Versus the even simpler model:
> "Return whether the number x is a NAN."

But what you're proposing doesn't match that description! Under your proposal, `math.isnan(10**1000)` would be `False`, but `math.isnan(Fraction(10**1000))` would again raise an `OverflowError`, as would `math.isnan(Decimal('1e500'))`.
msg274598 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2016-09-06 18:48
> as would `math.isnan(Decimal('1e500'))`

Whoops, no. I'd forgotten that large finite `Decimal` objects end up as `float` infinities under conversion. Not sure I like that much, but it is what it is ...
msg274859 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2016-09-07 18:28
Closing this as "won't fix". I agree with Tim that the right way to handle this is to make math.isnan behave like math.floor and math.ceil currently do, via new special methods, but (1) I think introducing new special methods should probably be a PEP-level change, and (2) I'm afraid I'm not motivated enough to implement it, so it's going to have to wait on someone who has that motivation.
History
Date User Action Args
2016-09-07 18:28:12mark.dickinsonsetstatus: open -> closed
type: behavior
messages: + msg274859

resolution: wont fix
stage: resolved
2016-09-06 18:48:45mark.dickinsonsetmessages: + msg274598
2016-09-06 18:43:24mark.dickinsonsetmessages: + msg274596
2016-09-06 18:34:12steven.dapranosetmessages: + msg274592
2016-09-06 18:18:24tim.peterssetnosy: + tim.peters
messages: + msg274586
2016-09-06 18:00:43mark.dickinsonsetmessages: + msg274580
2016-09-06 17:59:08mark.dickinsonsetmessages: + msg274578
2016-09-06 17:47:18steven.dapranosetmessages: + msg274573
2016-09-06 17:30:40mark.dickinsonsetmessages: + msg274572
2016-09-06 17:24:33mark.dickinsonsetnosy: + mark.dickinson
messages: + msg274571
2016-09-06 17:04:33steven.dapranocreate