Pre-PEP: Imaginary type and IEC 60559-compatible complex arithmetic

Some notes to the PEP:

  • You described how the repr and parsing of complex is changed and how the repr of imaginary looks, but what about parsing string to imaginary?
  • An imaginary type will require support in the marshal format, a bump of the marshal version.
  • It will also require changes in ast.literal_eval() and maybe other ast functions (ast.unparse()?).
1 Like

Ok, I reopened the draft PR.

In current shape — there is no imaginary builtin (though, it can accessed by type(1j)). Do you think we need this from beginning? I put that question in “Open Issues” so far.

String parsing for now — available with eval(), ast.literal_eval(). You are right, I’ll add this to the Specification section.

All this is working (I hope, correctly) in the draft implementation:

>>> import ast, marshal
>>> ast.literal_eval('-1j')
-1j
>>> type(_)
<class 'imaginary'>
>>> ast.literal_eval('1+1j')
(1+1j)
>>> type(_)
<class 'complex'>
>>> marshal.dumps(-1j)
b'J\x00\x00\x00\x00\x00\x00\xf0\xbf'
>>> marshal.loads(_)
-1j

These adjustments are more or less obvious. Should I mention them in the PEP?

marshal needs a special code to distinguish imaginary -1j from complex 0-1j. This requires a new version of the marshal format. Usually this required a PEP, but I think we can count this PEP.

Note also that an imaginary will be authomatically converted to complex when stored in an array or a NumPy array, when packed with struct.pack() and when passed to cmath functions and other functions that use the corresponding PyArg_Parse() format unit. So the difference between imaginary and complex will be lost in these cases.

1 Like

Ah, and what about pickling? Normally we need the type to be accessible by name. Not necessary as a builtin imaginary, but maybe as complex.imaginary or types.ImaginaryType.

1 Like

(Well, version increment was lost during PEP preparation :slight_smile: Probably, it incremented in the main during this time…)

Hmm, we don’t have code for complex numbers here (though, maybe we should, as for struct/ctypes). Did I miss something?

Not, if you are using dtype=object. But if you store in homogenous complex array… — then expect, that imaginary values will be converted to this. Just as floats.

I thought about this and here we don’t have many options, probably. As for floats:

>>> struct.pack('D', 1.1)
b'\x9a\x99\x99\x99\x99\x99\xf1?\x00\x00\x00\x00\x00\x00\x00\x00'
>>> struct.unpack('D', _)
((1.1+0j),)

I was thinking about something like cmath.cos(1j) -> math.cosh(1) (~ Annex G.7), i.e. make cmath’s functions being type-dependent (as in the gmpy2 if allow_complex is True for context). But that, probably, introduce too much code breakages (if someone does type checks on output).

The “D” format is fine for PyArg_Parse(), as imaginary numbers are complex subtype.
I guess, you meant Py_BuildValue(). Here maybe new format type does make sense,
but I doubt we need this, lets do it on demand. For now, we have PyImaginary_FromDouble() API.

Yeh, pickling of imaginary values was broken, when I reverted back the builtin.
(To my surprise, this wasn’t catched by tests. We have few tests on pickling complex numbers.)
Now this is fixed with a dedicated reducer for the copyreg.

Probably, this does make sense.