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: Complex numbers with negative zero parts do not roundtrip properly
Type: behavior Stage:
Components: Versions: Python 3.6
process
Status: closed Resolution: wont fix
Dependencies: Superseder:
Assigned To: Nosy List: mark.dickinson, r.david.murray, veky
Priority: normal Keywords:

Created on 2016-06-21 19:02 by veky, last changed 2022-04-11 14:58 by admin. This issue is now closed.

Messages (17)
msg269013 - (view) Author: Vedran Čačić (veky) * Date: 2016-06-21 19:02
Look:

    >>> complex('1-0j')
    (1-0j)
    >>> 1-0j
    (1+0j)

Yes, I understand what's going on, and it's probably wrong / too much to expect 1-0j to work properly, but I'd really like the complex from string constructor to be consistent with that. Even more because (of course):

    >>> import ast
    >>> ast.literal_eval('1-0j')
    (1+0j)
msg269014 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2016-06-21 19:27
This has been discussed in multiple issues before this one. Currently, `complex` from a string provides one of two ways to get a complex number with the correct signs for the real and imaginary parts; I'd hate to change that to give wrong results instead.

The other important property here is that `complex(repr(z))` recovers `z` for a complex number `z`. That would break with your suggested change.

Strong -1 from me.
msg269015 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2016-06-21 19:38
Related: #17336, #22548, #25839
msg269113 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2016-06-23 14:35
Breaking the repr invariant would be bad.  Agree with rejection.
msg269144 - (view) Author: Vedran Čačić (veky) * Date: 2016-06-23 21:32
Funny, I thought the "repr invariant" was exactly the opposite thing, that _eval_ (or literal_eval) of repr should give back the starting object. And that's what I intended to preserve here. It's obviously broken now.

Ok, alternate suggestion: can at least ast.literal_eval be fixed to work in the same way as complex constructor? Because it's not much of a "literal" otherwise...
msg269181 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2016-06-24 14:39
That's a good point; however the goal of the "repr invariant" is to be able to losslessly reproduce the object when possible.  The fact that you have to use the complex constructor to do that is unfortunate, but is a consequence of the underlying problem.  I suspect that literal_eval, on the other hand, should reproduce what the interpreter does, but since I think it already doesn't do that 100% (though I can't offhand remember what makes me think that) perhaps that is a possibility.  I'll reopen the issue to discuss that.
msg269189 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2016-06-24 15:55
> I suspect that literal_eval, on the other hand, should reproduce what the interpreter does

I think that's going to be awkward to achieve without making the behaviour of literal_eval significantly less obvious and more DWIMmy. And I'm not convinced that `literal_eval` should follow the behaviour of the complex constructor rather than the behaviour of plain `eval`.

Of course, the "right" fix here is to change the complex repr entirely so that it looks like the compound object that it is rather than an eval-able expression:

>>> repr(1+2j)
complex(1.0, 2.0)

That would break backwards compatibility, but given the number of times complaints come up on this tracker, I'm beginning to think it might be worth it.
msg269233 - (view) Author: Vedran Čačić (veky) * Date: 2016-06-25 12:22
Mark, I think it would be a great idea. It would be consistent with both "str is straightforward, repr is reproducible", and with the idea that (evalable) repr is almost always of the form `typename(arguments)`. It would also get rid of that "supefluous-looking" parentheses around the repr, and put them in the right place: a call. :-)

[ On the other hand, I started to think deeply about the reason why we cannot make 1-0j do the right thing, and I'm not so sure anymore. People usually say it's because we don't have separate imaginary type, but we don't need it: all we need is a separate _real_ type, and we have it: float.

When Python subtracts 0j from a float 1.0, there is no absolute imperative that it has to do it in the same way as subtracting 0j from a complex(1.0, 0.0). We _can_ make it so the result of the former is complex(1.0, -0.0), and result of the latter is complex(1.0, 0.0).

Of course, now even the imaginary complex numbers couldn't be outputed simply, and -0 real part should become -0.0, but it might be worth it. I understand it's enormously complicated though, and I'd be satisfied with a "normal" repr. Or a literal_eval that really understand complex numbers' repr. ]
msg269242 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2016-06-25 15:17
> People usually say it's because we don't have separate imaginary type, but we don't need it.

I think we do. Consider the case of something like -0 + 1j (the `repr` of complex(-0.0, 1.0)). That currently evaluates to `complex(0.0, 1.0)`, because the `-0.0` is combined with the `+0.0` real part of `1j`.
msg269270 - (view) Author: Vedran Čačić (veky) * Date: 2016-06-26 01:05
Yes, but IMO that's a separate issue. And if complex analysis has taught me anything, it's that the sign of zero of .imag is much more important than the sign of zero of .real part (most elementary functions have branch cuts along real axis, where sign of .imag ensures continuity on both sides). Of course, having both would be even better, but having only this part is a good part of a good thing.

However, as I said, I know it's complicated. How about giving a "conventional" repr to complex? As far as I see, it's really not hard to implement - the only problem is backwards compatibility. But that was a problem when parentheses were added, too, right?

[ And there would be one more benefit: We could finally say goodbye to  weird "names" (infj, nanj) in the repr. By analogy with float, this could just be complex('nan', '-inf') or whatever. ]

For what it's worth, I'm not sure we should try too hard to preserve complex(repr(z)) being z given isinstance(z, complex). For example, Fraction and Decimal don't have this property (while it does kinda hold for str instead of repr, and it would continue to kinda hold for str here). Yes, I know Fraction and Decimal aren't builtins and complex is, but I really think it's only because of syntax support for j-based literals.
msg269468 - (view) Author: Vedran Čačić (veky) * Date: 2016-06-29 08:51
And of course, the most important argument that preserving type(x)(repr(x))==x is futile: try it with x=False. (Yes, bools are numbers too.:)

On the other hand, of course, literal_eval treats 'False' completely fine.
msg269496 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2016-06-29 14:09
Right, that isn't the invariant.  Eval is the normal invariant, but not all classes *can* meet it, in which case if they can provide a repr that *can* be turned back into the value losslessly somehow, that's better than not doing so at all.

Still, changing the repr would be the best way to meet the desired invariant, if we're willing to do that.  This could be the first stdlib case of a non-string having a useful __str__ if we make the __str__ still return what the current __repr__ does.
msg269500 - (view) Author: Vedran Čačić (veky) * Date: 2016-06-29 14:21
...but hopefully not the last. People are playing (with BDFL's blessing) with the idea of types having just (qual)name as str.

By "first stdlib case" you mean "first builtin case", right? fractions.Fraction and decimal.Decimal are in stdlib. :-)

Yes, not all classes can (nor should) support the strong "literal_eval being left-inverse of repr" invariant, but IMO builtin value-like (having a meaningful ==/hash infrastructure separate from identity) classes should. Numbers are surely in that camp.
msg269505 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2016-06-29 14:37
Ah, I haven't used Fractions and Decimal much, so I hadn't noticed.  Those postdate complex, I think, but do follow the model Mark is suggesting.  Seems like it would be reasonble to make complex do the same...the concern of course is backward compatibilty.
msg273421 - (view) Author: Vedran Čačić (veky) * Date: 2016-08-23 08:21
Unfortunately, as http://bugs.python.org/issue23229#msg233965 shows, Guido is against changing complex.__repr__. Is there any chance someone  could show this discussion to him, to show how it would help and try to change his mind?
msg273438 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2016-08-23 12:41
> Unfortunately, as http://bugs.python.org/issue23229#msg233965 shows, Guido is against changing complex.__repr__.

Good catch! I think that means we should close this issue. The current behaviour isn't actually wrong; it's just a compromise between lots of different concerns.
msg273473 - (view) Author: Vedran Čačić (veky) * Date: 2016-08-23 16:00
Since I'm so happy with Fraction being fixed, I'll agree here. :-)
History
Date User Action Args
2022-04-11 14:58:32adminsetgithub: 71550
2016-08-23 16:00:45vekysetmessages: + msg273473
2016-08-23 12:41:13mark.dickinsonsetstatus: open -> closed
resolution: wont fix
messages: + msg273438
2016-08-23 08:21:50vekysetmessages: + msg273421
2016-07-03 01:16:11ppperrysettitle: Complex numbers with negative zero imaginary parts do not roundtrip properly -> Complex numbers with negative zero parts do not roundtrip properly
2016-06-29 16:16:46ppperrysettitle: Complex with negative zero imaginary part -> Complex numbers with negative zero imaginary parts do not roundtrip properly
2016-06-29 14:37:58r.david.murraysetmessages: + msg269505
2016-06-29 14:21:39vekysetmessages: + msg269500
2016-06-29 14:09:01r.david.murraysetmessages: + msg269496
2016-06-29 08:51:47vekysetmessages: + msg269468
2016-06-26 01:05:26vekysetmessages: + msg269270
2016-06-25 15:17:51mark.dickinsonsetmessages: + msg269242
2016-06-25 12:22:50vekysetmessages: + msg269233
2016-06-24 15:55:24mark.dickinsonsetmessages: + msg269189
2016-06-24 14:39:34r.david.murraysetstatus: closed -> open
resolution: rejected -> (no value)
messages: + msg269181

stage: resolved ->
2016-06-23 21:32:06vekysetmessages: + msg269144
2016-06-23 14:35:27r.david.murraysetstatus: open -> closed

nosy: + r.david.murray
messages: + msg269113

resolution: rejected
stage: resolved
2016-06-21 19:38:12mark.dickinsonsetmessages: + msg269015
2016-06-21 19:27:37mark.dickinsonsetnosy: + mark.dickinson
messages: + msg269014
2016-06-21 19:02:08vekycreate