This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: math.isfinite() can raise exception when called on a number
Type: enhancement Stage:
Components: Documentation Versions: Python 3.11
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: docs@python Nosy List: Nathaniel Manista, docs@python, mark.dickinson, rhettinger, serhiy.storchaka, steven.daprano, tfish2, tim.peters
Priority: normal Keywords:

Created on 2022-03-25 17:28 by tfish2, last changed 2022-04-11 14:59 by admin.

Messages (8)
msg416010 - (view) Author: Thomas Fischbacher (tfish2) Date: 2022-03-25 17:28
>>> help(math.isfinite)
isfinite(x, /)
    Return True if x is neither an infinity nor a NaN, and False otherwise.

So, one would expect the following expression to return `True` or `False`. We instead observe:

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

(There likewise is a corresponding issue with other, similar, functions).

This especially hurts since PEP-484 states that having a Sequence[float] `xs` does not allow us to infer that `all(issubclass(type(x), float) for x in xs)` actually holds - since a PEP-484 "float" actually does also include "int" (and still, issubclass(int, float) == False).

Now, strictly speaking, `help(math)` states that

DESCRIPTION
    This module provides access to the mathematical functions
    defined by the C standard.

...but according to "man 3 isfinite", the math.h "isfinite" is a macro and not a function - and the man page does not show type information for that reason.
msg416011 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2022-03-25 17:52
The math.isfinite() docs could be changed to something like, "coerces x to a float if possible and then returns True if x is neither an infinity nor a NaN, and False otherwise."  Or there could be a general note about which functions (most of them) coerce to float (which can fail).

With respect to typing and PEP-484, I don't see a bug or documentation issue.  Types relationships are useful for verifying which methods are available, but they don't make promises about the range of valid values.  For example math.sqrt(float) -> float promises which types are acceptable but doesn't promise that negative inputs won't raise an exception.  Likewise, "n: int=10; len(range(n))" is type correct but will raise an OverflowError for "n = 10**100".
msg416018 - (view) Author: Thomas Fischbacher (tfish2) Date: 2022-03-25 21:57
The problem with PEP-484 is that if one wants to use static type analysis, neither of these options are good:

- Use static annotations on functions, and additionally spec
  out expectations in docstrings. Do note that the two types places
  where "float" is mentioned here refer to different concepts.
  This looks as if there were duplication, but there actually
  isn't, since the claims are different. This is confusing as hell.

def foo(x: float) -> float:
  """Foos the barbaz

  Args:
    x: float, the foobar
  Returns:
    float, the foofoo"""

The floats in the docstring give me a guarantee: "If I feed in a float, I am guaranteed to receive back a float". The floats in the static type annotation merely say "yeah, can be float or int, and I'd call it ok in these cases" - that's a very different statement.

- Just go with static annotations, drop mention of types
  from docstrings, and accept that we lose the ability to
  stringently reason about the behavior of code.

With respect to this latter option, I think we can wait for "losing the ability to stringently reason about the behavior of code" to cause major security headaches. That's basically opening up the door to many problems at the level of "I can crash the webserver by requesting the url http://lpt1".
msg416040 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2022-03-26 05:55
Most (but not all) functions in the math module implicitly convert its arguments to float. Here we can get an OverflowError. Do we want to add a note to every function that does it? Or add a general note at the top of the file and add exclusion notes to functions which do not convert arguments to float?

BTW, there is a general note: "Except when explicitly
noted otherwise, all return values are floats."
msg416840 - (view) Author: Steven D'Aprano (steven.daprano) * (Python committer) Date: 2022-04-06 02:51
Isn't this just a quality of implementation issue?

math.isfinite should return True for all ints, since all ints are finite. (There are no int infinities or NANs). There is no need to coerce them to float to know that they are finite.

Likewise for Fractions. If they overflow, that could be caught and True returned.

Decimal infinities convert to float infinities, so the only way you can get an overflow error is if the Decimal is finite but too big to convert. So again, isfinite could (should?) catch the overflow error and return True.

Any numeric type that has an infinity which does not coerce to float infinity, but overflows instead, is buggy, and its not isfinite's responsibility to protect against that.

So here is my suggestion:

isfinite() should return True for all ints, without needing to coerce them to float. For other numeric types, it should try to coerce them to float, and catch OverflowError and raise True. This should be documented, so that other numeric types know what contract they are required to follow (infinity coerces to float infinity).

I'm going to change this to an enhancement for 3.11 (or 3.12).
msg416842 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2022-04-06 03:58
> isfinite() should return True for all ints, without needing
> to coerce them to float

Whoa there.  You're venturing into changing what those math functions were all about and the core approach to how they operate.

A zigzag to this new direction would need discussion with all the math folks.  Many tools in the math module are thin wrapper around libmath and we never intended to be universal handlers of all numeric types.  We have a lot of functions that start by coercing their input to a float.

My two cents is that this would open an endless can of worms and make everyone pay a time and complexity cost for a feature that almost no one needs.  AFAICT is more of a purity issue than an actual practical need that would warrant separate code paths (for example, we recently closed an issue where someone wanted math.log() to have a separate code path for Fraction inputs where it return log(f.numerator) - log(f.denominator) with the goal of handling fractional values smaller than float_min).
msg416843 - (view) Author: Tim Peters (tim.peters) * (Python committer) Date: 2022-04-06 04:13
I'll testify that I won't volunteer one second of my time pursuing these abstract "purity" crusades ;-) `isfinite()` et alia were added to supply functions defined by current standards to work on IEEE floating-point values. People working with floats have reasonable expectations that Python will supply workalikes for industry-stand float functions.

If was had to cater to all possible "numberish" types, we'd never add anything new again :-( Doing that in a principled way requires dedicated new dunder methods, and that's a high bar.

That I said, I extended math.log() decades ago to work on giant integers. It was useful for real projects I was working on at the time - I was scratching my own itches.
msg416969 - (view) Author: Thomas Fischbacher (tfish2) Date: 2022-04-08 10:09
Tim, the problem may well be simply due to the documentation of math.isfinite() being off here.

This is what we currently have:

https://docs.python.org/3/library/math.html#math.isfinite

===
math.isfinite(x)
Return True if x is neither an infinity nor a NaN, and False otherwise. (Note that 0.0 is considered finite.)

New in version 3.2.
===

If this were re-worded as follows (and corresponding changes were made to other such functions), everyone would know what the expectations and behavior are:

===
math.isfinite(x)

If `x` is a `float` instance, this evaluates to `True` if `x` is
neither a float infinity nor a NaN, and `False` otherwise.
If `x` is not a `float` instance, this is evaluates to
`math.isfinite(float(x))`.

New in version 3.2.
===

This would be an accurate defining description of the actual behavior. Note that, "thanks to PEP-484", this abbreviation would currently be ambiguous though:

===
math.isfinite(x)

If `x` is a float, this evaluates to `True` if `x` is
neither a float infinity nor a NaN, and `False` otherwise.
If `x` is not a float, this is evaluates to
`math.isfinite(float(x))`.

New in version 3.2.
===

("ambiguous" since "float" means different things as a static type and as a numbers class - and it is not clear what would be referred to here).

Changing/generalizing the behavior might potentially be an interesting other proposal, but I would argue that then one would want to change the behavior of quite a few other functions here as well, and all this should then perhaps go into some other `xmath` (or so) module - bit like it is with `cmath`.

However, since the Python philosophy is to not rely on bureaucracy to enforce contracts (as C++, Java, etc. do it), but instead to rely on people's ability to define their own contracts, making the math.isfinite() contract more accurate w.r.t. actual behavior in the CPython implementation via extra clarification looks like a good thing to do, no?
History
Date User Action Args
2022-04-11 14:59:57adminsetgithub: 91277
2022-04-08 10:09:13tfish2setmessages: + msg416969
2022-04-06 04:13:57tim.peterssetmessages: + msg416843
2022-04-06 03:58:49rhettingersetnosy: + tim.peters, mark.dickinson
messages: + msg416842
2022-04-06 02:51:00steven.dapranosetversions: + Python 3.11
nosy: + steven.daprano

messages: + msg416840

type: enhancement
2022-03-26 05:55:57serhiy.storchakasetnosy: + serhiy.storchaka
messages: + msg416040
2022-03-25 21:57:44tfish2setmessages: + msg416018
2022-03-25 17:52:35rhettingersetnosy: + rhettinger, docs@python
messages: + msg416011

assignee: docs@python
components: + Documentation
2022-03-25 17:29:44Nathaniel Manistasetnosy: + Nathaniel Manista
2022-03-25 17:28:43tfish2create