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

Addition/subtraction clear sign from signed 0j #107854

Closed
skirpichev opened this issue Aug 11, 2023 · 8 comments
Closed

Addition/subtraction clear sign from signed 0j #107854

skirpichev opened this issue Aug 11, 2023 · 8 comments
Labels
interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error

Comments

@skirpichev
Copy link
Contributor

Complex numbers with signed zero as an imaginary part could be created from a string or with two-argument form of the complex() constructor:

>>> complex(1.0, -0.0)
(1-0j)
>>> complex('(1-0j)')
(1-0j)

It's also possible to create pure imaginary numbers with signed zero:

>>> -0j  # or it should be complex(0.0,-0.0)?
(-0-0j)

But it's impossible to create a complex number with signed zero as an imaginary part if addition/subtraction is used (i.e. adding a real number and an imaginary number):

>>> 1-0j  # should be complex(1.0,-0.0)
(1+0j)
>>> 1+(-0j)  # ditto here
(1+0j)
>>> 0-0j  # should be complex(0.0,-0.0)
0j
@skirpichev skirpichev added the type-bug An unexpected behavior, bug, or error label Aug 11, 2023
@terryjreedy terryjreedy added interpreter-core (Objects, Python, Grammar, and Parser dirs) pending The issue will be closed if no feedback is provided labels Aug 11, 2023
@terryjreedy
Copy link
Member

Please find something in the docs that says the above is a bug. AFAIK, a real + complex is complex(real) + complex. 1 - 0j is (1.0+0j) - (0.0+0.0j) = (1.0-0.0) + (0.0-0.0)j = 1.0 + 0.0j. The latter is displayed as (1+0j), but the .real and .imag components are the floats given.
@ericvsmith

@skirpichev
Copy link
Contributor Author

@terryjreedy, probably you are right: the docs says there are no complex literals (complex numbers can be formed by adding a real number and an imaginary number), so the mechanics of current behaviour is transparent.

On another hand:

  1. some complex object can't be created with using available numeric literals (e.g. complex(0.0, -0.0) or complex(-1.0,-0.0)).
  2. repr(complex(1.1,-0.0)) returns '(1.1-0j)', using float and imaginary literals. But according to the docs it should be "a string that would yield an object with the same value when passed to eval()", which is not true. At least this seems to be a (minor) issue, not an enhancement proposal.
  3. other languages (e.g. Scheme) have complex literals.

@mdickinson
Copy link
Member

To the originally reported issue: indeed this isn't a bug, and everything's working as designed here. As @terryjreedy says, addition simply adds the real parts and imaginary parts independently of each other, and addition between a float and complex promotes the float to complex first (adding a (positive) zero imaginary part).

On the repr of a complex number, this is a topic already much explored on this tracker. #84450 is one relevant issue, but you'll find many other related issues linked to that one.

On changing the language to add a complex literal: that's something that would need to be discussed at discuss.python.org first.

@mdickinson mdickinson closed this as not planned Won't fix, can't repro, duplicate, stale Aug 11, 2023
@skirpichev
Copy link
Contributor Author

On the repr ... #84450 is one relevant issue, but you'll find many other related issues linked to that one.

@mdickinson, all linked issues are closed as well, while problem seems to be valid...

On changing the language to add a complex literal: that's something that would need to be discussed at discuss.python.org first.

Will you +1 on this idea?

BTW, there is a different option (probably it's too weird to be considered before):

diff --git a/Objects/complexobject.c b/Objects/complexobject.c
index 0e96f54584..e362982719 100644
--- a/Objects/complexobject.c
+++ b/Objects/complexobject.c
@@ -473,6 +473,9 @@ complex_sub(PyObject *v, PyObject *w)
     Py_complex a, b;
     TO_COMPLEX(v, a);
     TO_COMPLEX(w, b);
+    if (!PyComplex_CheckExact(v)) {
+        a.imag = copysign(0.0, -b.imag);
+    }
     result = _Py_c_diff(a, b);
     return PyComplex_FromCComplex(result);
 }

then

>>> 1-0j
(1-0j)
>>> complex(1.0,-0.0)
(1-0j)

On cons: this will break current float+complex(real,±0) <=> complex(float,0.0)+complex(real,±0). But on pros - this matches with the current repr for complex objects.

@mdickinson mdickinson removed the pending The issue will be closed if no feedback is provided label Aug 17, 2023
@mdickinson
Copy link
Member

all linked issues are closed as well

Indeed they are! That's because (a) there's no bug here, and (b) no-one has proposed a behaviour change that's been accepted as both an improvement on the status quo and worth the cost of migration.

while problem seems to be valid

By "problem" here, I guess you're referring to the fact that eval(repr(z)) doesn't always recover z exactly. That property is a nice-to-have, but (as in this case) it's not always achievable; its absence doesn't constitute a bug. But this has already been gone over many times in the linked issues, and I don't think it's helpful to rehash the discussion yet again in another new issue.

Will you +1 on this idea?

No idea: the simple-looking statement "add a complex literal to Python" hides a wealth of details that would have to be spelled out in any proposal. That's one reason that the discussion would be better over on https://discuss.python.org, so that those details and their consequences can be hashed out.

But FWIW, I'd have a hard time supporting a proposal that broke referential transparency to give x = 1.0; x = x - 0j and x = 1.0 - 0j different meanings.

@skirpichev
Copy link
Contributor Author

you're referring to the fact that eval(repr(z)) doesn't always recover z exactly

Of course.

it's not always achievable

Hardly it's the case here. Simplest solution is repr(complex(x, y))=='complex(x,y)'. Unfortunately, this has strong -1 from Guido. But you were +1.

@mdickinson, what do you think if we restrict the repr() format (not str()!) to this form only for some complex objects, i.e. complex(nonzero, -0.0) with the rest untouched? (More simple version of #19593.) BTW the current repr() format is already not uniform: e.g. sometimes we omit brackets. Will you +1 on this? I think this might clear -1 from Guido, because more verbose form will be restricted to a limited number of edge cases.

its absence doesn't constitute a bug

The docs seems to be more strict about this: "this function makes an attempt to return a string that would yield an object with the same value when passed to eval(); otherwise, the representation is a string enclosed in angle brackets that contains the name of the type of the object together with additional information" (c) https://docs.python.org/3/library/functions.html#repr

(There are round brackets, not angle...)

@mdickinson
Copy link
Member

what do you think if we restrict the repr() format (not str()!) to this form only for some complex objects, [...]

Meh. I'm not excited by the idea - the repr format is already messy enough (as you point out, sometimes getting parentheses and sometimes not), and adding even more variations to it doesn't seem like an improvement. It would also be disruptive to those parsing the current repr output.

The repr also currently has the property that complex(repr(z)) recovers z for any complex z. If we wanted to preserve that, complex would have to be changed to accept strings of the form "complex(...)".

We've tinkered with the complex representation several times (mostly in Python 2.x); every time we did that it caused some disruption to existing code and third-party packages. I think what we have right now is a reasonable compromise between the various constraints (with backwards compatibility being one of those constraints), and I'm honestly not enthusiastic about making any further changes at this point; I think the time for that has passed. Any change at this point would need to demonstrate significant added value to compensate for the disruption.

The docs seems to be more strict about this [...]

That's now how I read those docs: I think the sentence you quote is descriptive rather than prescriptive (it starts with "For many types, ..."). And the doc statement is true, but complex is not one of those "many types".

If you want to take this further, please do start a discussion on discuss.python.org. As I think I've made clear, I'm opposed to making any changes to what we currently have, but if there's a strong consensus for changing the complex repr then obviously we should rethink. We're not going to find that consensus by discussing on a closed issue that no-one besides the two of us is reading, though.

@skirpichev
Copy link
Contributor Author

JFR: https://discuss.python.org/t/33433

PS:

We've tinkered with the complex representation several times (mostly in Python 2.x)

Sorry, I've failed to find remnants of this in the mail lists (at least something, that will worth mentioning in the post).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
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