Skip to content

Improve docs of math.isclose(0, x) when x = 0 #131094

Closed

Description

@guyj-s1

Documentation

In the documentation of math.isclose it states

When comparing x to 0.0, isclose(x, 0) is computed as abs(x) <= rel_tol * abs(x), which is False for any x and rel_tol less than 1.0.

The statement isn't correct since x = 0 will give True

Linked PRs

Activity

  1. hoodmane commented on Mar 11, 2025

    @hoodmane
    Contributor

    Seems the correction is to add a single word:

    When comparing x to 0.0, isclose(x, 0) is computed as abs(x) <= rel_tol * abs(x), which is False for any nonzero x and rel_tol less than 1.0.

    Would you mind making a PR?

  2. eendebakpt commented on Mar 11, 2025

    @eendebakpt
    Contributor

    When making the PR also update the formatting (the parameters should be emphasized). I think the suggested change to the doc is an improvement, although with floating point arithmetic there are some corner cases:

    import math
    
    x = 1e-320
    rel_tol = 1e-9
    abs_tol = math.nextafter(1, 0)
    print(math.isclose(x, 0, rel_tol=rel_tol, abs_tol=abs_tol))
    
    print(abs(x) <= rel_tol * abs(x))
    print(abs(x) <= abs(rel_tol * x))
    

    results in

    True
    False
    False
    
  3. plashchynski commented on Mar 11, 2025

    @plashchynski
    Contributor

    @eendebakpt it's true because the difference is less than abs_tol:

    import math
    
    x = 1e-320
    rel_tol = 1e-9
    abs_tol = math.nextafter(1, 0)
    
    diff = abs(0 - x);
    print(diff <= abs_tol) # returns True

    I think this statement in the documentation holds only for abs_tol being zero (which is the default).

  4. eendebakpt commented on Mar 11, 2025

    @eendebakpt
    Contributor

    The relevant code is:

    cpython/Modules/mathmodule.c

    Lines 3202 to 3211 in ad90c5f

    /* now do the regular computation
    this is essentially the "weak" test from the Boost library
    */
    diff = fabs(b - a);
    return (((diff <= fabs(rel_tol * b)) ||
    (diff <= fabs(rel_tol * a))) ||
    (diff <= abs_tol));
    }

    @plashchynski I agree the statement is valid if f abs_tol is zero. The way the documentation is written suggest the statement is true in general though (if b=0).

  5. plashchynski commented on Mar 11, 2025

    @plashchynski
    Contributor

    @eendebakpt imho this statements sounds more like a reason to set abs_tol to a value greater than zero:

    *abs_tol* is the absolute tolerance; it defaults to ``0.0`` and it must be
    nonnegative. When comparing ``x`` to ``0.0``, ``isclose(x, 0)`` is computed
    as ``abs(x) <= rel_tol * abs(x)``, which is ``False`` for any ``x`` and
    rel_tol less than ``1.0``. So add an appropriate positive abs_tol argument
    to the call.

  6. plashchynski commented on Mar 11, 2025

    @plashchynski
    Contributor

    Btw, if a = math.nextafter(0, 1) then math.isclose(a, 0) will always be true for any rel_tol > 0:

    import math
    a = math.nextafter(0, 1)
    print(math.isclose(a, 0, rel_tol=0.9)) # True

    because:

    print(math.nextafter(0, 1) * 0.9 == math.nextafter(0, 1)) # True

    which is contrary to this statement and to pure mathematics as well.

    Is there a policy for such corner cases? Should they be considered and handled?

  7. skirpichev commented on Mar 12, 2025

    @skirpichev
    Member

    @eendebakpt, I think you misread documentation. It looks like abs_tol disappear in equation of the paragraph, which deals with comparing x to 0, but it's because abs(x) <= rel_tol * abs(x) is valid only in case of abs_tol=0.0 (isclose(x, 0) means the default value of abs_val). Last sentence suggests what to do in this case: "So add an appropriate positive abs_tol argument to the call."

    For nonzero abs_tol you must use the generic equation, mentioned right at the beginning of docs: abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol).

    So, in your example:

    >>> import math
    ... 
    ... x = 1e-320
    ... rel_tol = 1e-9
    ... abs_tol = math.nextafter(1, 0)
    ... 
    >>> math.isclose(x, 0, rel_tol=rel_tol, abs_tol=abs_tol)
    True
    >>> abs(x) <= max(rel_tol * abs(x), abs_tol)
    True

    @hoodmane fix looks good for me. Though, I'm not sure we need such pedancy in docs.

  8. added a commit that references this issue on Mar 12, 2025
  9. added a commit that references this issue on Mar 12, 2025
  10. changed the title [-]math.isclose(0, x) will be True if x = 0[/-] [+]Improve docs of `math.isclose(0, x)` when `x = 0`[/+] on Mar 12, 2025
  11. added a commit that references this issue on Mar 18, 2025
  12. added 2 commits that reference this issue on Mar 18, 2025
  13. picnixz commented on Mar 18, 2025

    @picnixz
    Member

    Thank you for the report and the fix!

  14. added 2 commits that reference this issue on Mar 18, 2025
  15. added a commit that references this issue on Mar 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    docsDocumentation in the Doc dir

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions