Skip to content

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

Closed

Description

@TomKrauss
mannequin
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']

    Activity

    1. TomKrauss commented on Feb 20, 2017

      TomKraussmannequin
      MannequinAuthor

      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)
    2. mdickinson commented on Feb 20, 2017

      @mdickinson
      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.

    3. self-assigned this
      on Feb 20, 2017
    4. mdickinson commented on Feb 20, 2017

      @mdickinson
      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)
    5. arigo commented on Feb 20, 2017

      arigomannequin
      Mannequin

      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.

    6. mdickinson commented on Feb 20, 2017

      @mdickinson
      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.

    7. mdickinson commented on Feb 20, 2017

      @mdickinson
      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.

    8. arigo commented on Feb 20, 2017

      arigomannequin
      Mannequin

      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.

    9. vstinner commented on Feb 20, 2017

      @vstinner
      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...

    10. mdickinson commented on Feb 20, 2017

      @mdickinson
      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.

    11. serhiy-storchaka commented on Feb 20, 2017

      @serhiy-storchaka
      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.

    12. arigo commented on Feb 20, 2017

      arigomannequin
      Mannequin

      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.

    13. mdickinson commented on Feb 20, 2017

      @mdickinson
      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?

    14. serhiy-storchaka commented on Feb 20, 2017

      @serhiy-storchaka
      Member

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

    15. mdickinson commented on Feb 20, 2017

      @mdickinson
      Member

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

    16. mdickinson commented on Feb 20, 2017

      @mdickinson
      Member

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

    17. mdickinson commented on Feb 20, 2017

      @mdickinson
      Member

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

    18. mdickinson commented on Feb 20, 2017

      @mdickinson
      Member

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

    19. mdickinson commented on Feb 20, 2017

      @mdickinson
      Member

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

    20. mdickinson commented on Feb 20, 2017

      @mdickinson
      Member

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

    21. mdickinson commented on Feb 20, 2017

      @mdickinson
      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!

    22. transferred this issue fromon Apr 10, 2022
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Assignees

    Labels

    3.7 (EOL)end of lifeinterpreter-core(Objects, Python, Grammar, and Parser dirs)type-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions