Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

complex() on object with __complex__ function loses sign of zero imaginary part #73788

Closed
TomKrauss mannequin opened this issue Feb 20, 2017 · 20 comments
Closed

complex() on object with __complex__ function loses sign of zero imaginary part #73788

TomKrauss mannequin opened this issue Feb 20, 2017 · 20 comments
Assignees
Labels
3.7 (EOL) end of life interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error

Comments

@TomKrauss
Copy link
Mannequin

TomKrauss mannequin commented Feb 20, 2017

BPO 29602
Nosy @arigo, @mdickinson, @vstinner, @stevendaprano, @serhiy-storchaka
PRs
  • bpo-29602: fix signed zero handling in complex constructor. #203
  • bpo-29602: fix signed zero handling in complex constructor #204
  • bpo-29602: fix signed zero handling in complex constructor #205
  • bpo-29602: fix signed zero handling in complex constructor #206
  • [Do Not Merge] Sample of CPython life with blurb. #703
  • [Do Not Merge] Convert Misc/NEWS so that it is managed by towncrier #552
  • Files
  • complex-constructor-from-complex.patch
  • complex-constructor-from-complex2.patch
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = 'https://github.com/mdickinson'
    closed_at = <Date 2017-02-20.22:04:35.292>
    created_at = <Date 2017-02-20.00:49:52.052>
    labels = ['interpreter-core', 'type-bug', '3.7']
    title = 'complex() on object with __complex__ function loses sign of zero imaginary part'
    updated_at = <Date 2017-03-31.16:36:13.628>
    user = 'https://bugs.python.org/TomKrauss'

    bugs.python.org fields:

    activity = <Date 2017-03-31.16:36:13.628>
    actor = 'dstufft'
    assignee = 'mark.dickinson'
    closed = True
    closed_date = <Date 2017-02-20.22:04:35.292>
    closer = 'mark.dickinson'
    components = ['Interpreter Core']
    creation = <Date 2017-02-20.00:49:52.052>
    creator = 'Tom Krauss'
    dependencies = []
    files = ['46653', '46654']
    hgrepos = []
    issue_num = 29602
    keywords = ['patch']
    message_count = 20.0
    messages = ['288177', '288192', '288196', '288205', '288207', '288208', '288210', '288211', '288212', '288223', '288224', '288229', '288231', '288233', '288236', '288240', '288242', '288245', '288246', '288247']
    nosy_count = 6.0
    nosy_names = ['arigo', 'mark.dickinson', 'vstinner', 'steven.daprano', 'serhiy.storchaka', 'Tom Krauss']
    pr_nums = ['203', '204', '205', '206', '703', '552']
    priority = 'normal'
    resolution = 'fixed'
    stage = 'resolved'
    status = 'closed'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue29602'
    versions = ['Python 2.7', 'Python 3.5', 'Python 3.6', 'Python 3.7']

    @TomKrauss
    Copy link
    Mannequin Author

    TomKrauss mannequin commented Feb 20, 2017

    Consider the following simple class that provides a "__complex__" method.

    class C(object):
      def __init__(self, x):
         self.x = x
      def __complex__(self):
        return self.x
    
    x=C(-0j)
    PYTHON 2.7.13
    >>> x.x
    -0j
    >>> complex(x)
    0j
    
    PYTHON 3.6
    >>> x.x
    (-0-0j)
    >>> complex(x)
    (-0+0j)

    @TomKrauss TomKrauss mannequin added the type-bug An unexpected behavior, bug, or error label Feb 20, 2017
    @mdickinson
    Copy link
    Member

    Confirmed on my machine. The effect of the source is to do complex(complex(x), 0.0) in this case, which isn't correct.

    @mdickinson mdickinson added 3.7 (EOL) end of life interpreter-core (Objects, Python, Grammar, and Parser dirs) labels Feb 20, 2017
    @mdickinson mdickinson self-assigned this Feb 20, 2017
    @mdickinson
    Copy link
    Member

    Looking at the source, there's also an issue with complex subclasses here:

    >>> class C(complex):
    ...     pass
    ... 
    >>> complex(C(-0j))
    (-0+0j)
    >>> complex(-0j)
    (-0-0j)

    @arigo
    Copy link
    Mannequin

    arigo mannequin commented Feb 20, 2017

    CPython 2.7 and 3.5 have issues with the sign of zeroes even without any custom class:

    >>> -(0j)       # this is -(0+0j)
    (-0-0j)
    >>> (-0-0j)     # but this equals to the difference between 0 and 0+0j
    0j
    >>> (-0.0-0j)   # this is the difference between -0.0 and 0+0j
    (-0+0j)
    >>> -0j
    -0j             # <- on CPython 2.7
    (-0-0j)         # <- on CPython 3.5

    It's unclear if the signs of the two potential zeroes in a complex number have a meaning, but the C standard considers these cases for all functions in the complex number's header.

    @mdickinson
    Copy link
    Member

    Armin: that's a separate issue, and is expected behaviour.

    >>> -(0j)       # this is -(0+0j)
    (-0-0j)

    Yes: 0j is complex(0.0, 0.0); negating negates both the real and imaginary parts.

    >>> (-0-0j)     # but this equals to the difference between 0 and 0+0j
    0j

    This is an operation between an integer (note that the initial negation is a no-op) and a complex. Here the integer gets promoted to complex(0.0, 0.0), and we do complex(0.0, 0.0) - complex(0.0, 0.0), which gives complex(0.0, 0.0).

    >>> (-0.0-0j)   # this is the difference between -0.0 and 0+0j
    (-0+0j)

    This is complex(-0.0, 0.0) - complex(0.0, 0.0). The real and imaginary parts are operated on separately, and in keeping with IEEE 754, the real part is evaluated as -0.0 - 0.0, which is -0.0.

    >>> -0j
    -0j             # <- on CPython 2.7
    (-0-0j)         # <- on CPython 3.5

    The Python 3 behaviour here is correct. The Python 2 behaviour is the result of an unfortunate AST optimization designed to ensure that -<sys.maxint+1> is an int rather than a long.

    None of the above is a new issue.

    @mdickinson
    Copy link
    Member

    It's unclear if the signs of the two potential zeroes in a complex number have a meaning

    Yes, very much so. The signs are important in determining which side of a branch cut to use in the various cmath functions.

    @arigo
    Copy link
    Mannequin

    arigo mannequin commented Feb 20, 2017

    Maybe I should be more explicit: what seems strange to me is that some complex numbers have a repr that, when entered in the source, produces a different result. For example, if you want the result (-0-0j) you have to enter something different. However, I missed the fact that calling explicitly complex(a, b) with a and b being floats always gives exactly a+bj with the correct signs. So I retract my comments.

    @vstinner
    Copy link
    Member

    IMHO it would be less surprising if repr(complex) would return 'complex(..., ...)': it would be possible to copy/paste and get the result value. I was bitten multiple time by the zero sign with complex numbers...

    @mdickinson
    Copy link
    Member

    Armin, Victor: please open other issues for these discussions; they're unrelated to the bug reported here. Also, see bpo-27363, bpo-17336, bpo-22548, bpo-25839.

    @serhiy-storchaka
    Copy link
    Member

    Proposed patch fixes constructing complex from complex.

    But in future versions perhaps it is worth to disallow passing complex argument to two-arguments complex constructor.

    @arigo
    Copy link
    Mannequin

    arigo mannequin commented Feb 20, 2017

    4 lines before the new "ci.real = cr.imag;", we have "cr.imag = 0.0; /* Shut up compiler warning */". The comment is now wrong: we really need to set cr.imag to 0.0.

    @mdickinson
    Copy link
    Member

    complex-constructor-from-complex2.patch looks good to me.

    What happens now? I presume we can no longer push to hg.python.org? So one of us needs to make a PR on GitHub and another review it?

    @serhiy-storchaka
    Copy link
    Member

    I pushed changes to my fork but when try to create a pull request it contains unrelated changes. Seems I do something wrong.

    @mdickinson
    Copy link
    Member

    Created #203, but I feel bad for having my name on the commits. :-(

    @mdickinson
    Copy link
    Member

    New changeset 112ec38 by GitHub in branch 'master':
    bpo-29602: fix signed zero handling in complex constructor. (#203)
    112ec38

    @mdickinson
    Copy link
    Member

    New changeset 4ddd897 by Mark Dickinson in branch 'bpo-29549-backport':
    bpo-29602: fix signed zero handling in complex constructor
    4ddd897

    @mdickinson
    Copy link
    Member

    New changeset c0b336e by GitHub in branch '2.7':
    bpo-29602: fix signed zero handling in complex constructor (#204)
    c0b336e

    @mdickinson
    Copy link
    Member

    New changeset 0936a00 by GitHub in branch '3.5':
    bpo-29602: fix signed zero handling in complex constructor. (#203) (#205)
    0936a00

    @mdickinson
    Copy link
    Member

    New changeset d9b3cdd by GitHub in branch '3.6':
    bpo-29602: fix signed zero handling in complex constructor. (#203) (#206)
    d9b3cdd

    @mdickinson
    Copy link
    Member

    I'm a bit puzzled about where the commit 4ddd897 came from, but as far as I can tell the changes to 2.7, 3.5, 3.6 and master are all okay.

    All fixed. Thanks for the report!

    @ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    3.7 (EOL) end of life interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error
    Projects
    None yet
    Development

    No branches or pull requests

    3 participants