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 numbers with negative zero parts do not roundtrip properly #71550

Closed
vedgar mannequin opened this issue Jun 21, 2016 · 17 comments
Closed

Complex numbers with negative zero parts do not roundtrip properly #71550

vedgar mannequin opened this issue Jun 21, 2016 · 17 comments
Labels
type-bug An unexpected behavior, bug, or error

Comments

@vedgar
Copy link
Mannequin

vedgar mannequin commented Jun 21, 2016

BPO 27363
Nosy @mdickinson, @bitdancer, @vedgar

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 = None
closed_at = <Date 2016-08-23.12:41:13.632>
created_at = <Date 2016-06-21.19:02:08.272>
labels = ['type-bug']
title = 'Complex numbers with negative zero parts do not roundtrip properly'
updated_at = <Date 2016-08-23.16:00:45.414>
user = 'https://github.com/vedgar'

bugs.python.org fields:

activity = <Date 2016-08-23.16:00:45.414>
actor = 'veky'
assignee = 'none'
closed = True
closed_date = <Date 2016-08-23.12:41:13.632>
closer = 'mark.dickinson'
components = []
creation = <Date 2016-06-21.19:02:08.272>
creator = 'veky'
dependencies = []
files = []
hgrepos = []
issue_num = 27363
keywords = []
message_count = 17.0
messages = ['269013', '269014', '269015', '269113', '269144', '269181', '269189', '269233', '269242', '269270', '269468', '269496', '269500', '269505', '273421', '273438', '273473']
nosy_count = 3.0
nosy_names = ['mark.dickinson', 'r.david.murray', 'veky']
pr_nums = []
priority = 'normal'
resolution = 'wont fix'
stage = None
status = 'closed'
superseder = None
type = 'behavior'
url = 'https://bugs.python.org/issue27363'
versions = ['Python 3.6']

@vedgar
Copy link
Mannequin Author

vedgar mannequin commented Jun 21, 2016

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)

@vedgar vedgar mannequin added the type-bug An unexpected behavior, bug, or error label Jun 21, 2016
@mdickinson
Copy link
Member

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.

@mdickinson
Copy link
Member

Related: bpo-17336, bpo-22548, bpo-25839

@bitdancer
Copy link
Member

Breaking the repr invariant would be bad. Agree with rejection.

@vedgar
Copy link
Mannequin Author

vedgar mannequin commented Jun 23, 2016

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

@bitdancer
Copy link
Member

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.

@bitdancer bitdancer reopened this Jun 24, 2016
@mdickinson
Copy link
Member

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.

@vedgar
Copy link
Mannequin Author

vedgar mannequin commented Jun 25, 2016

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. ]

@mdickinson
Copy link
Member

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.

@vedgar
Copy link
Mannequin Author

vedgar mannequin commented Jun 26, 2016

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.

@vedgar
Copy link
Mannequin Author

vedgar mannequin commented Jun 29, 2016

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.

@bitdancer
Copy link
Member

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.

@vedgar
Copy link
Mannequin Author

vedgar mannequin commented Jun 29, 2016

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

@bitdancer
Copy link
Member

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.

@pppery pppery mannequin changed the title Complex with negative zero imaginary part Complex numbers with negative zero imaginary parts do not roundtrip properly Jun 29, 2016
@pppery pppery mannequin changed the title Complex numbers with negative zero imaginary parts do not roundtrip properly Complex numbers with negative zero parts do not roundtrip properly Jul 3, 2016
@vedgar
Copy link
Mannequin Author

vedgar mannequin commented Aug 23, 2016

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?

@mdickinson
Copy link
Member

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.

@vedgar
Copy link
Mannequin Author

vedgar mannequin commented Aug 23, 2016

Since I'm so happy with Fraction being fixed, I'll agree here. :-)

@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
type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

2 participants