Search code examples
pythonpython-3.xcompatibilitycomplex-numbersstring-conversion

Replicate Python's complex-number-to-string conversion


import operator
>>> operator.truediv(-5.6, complex(-1, 0)) #eg1
(5.6-0j)
>>> operator.truediv(-5.6, complex(0, -1)) #eg2
(-0-5.6j)
>>> operator.truediv(-5.6, complex(0, 1)) #eg3
5.6j
>>> operator.truediv(-5.6, complex(1, 0)) #eg4
(-5.6+0j)
>>> operator.truediv(5.6, complex(0,-1)) #eg 5
(-0+5.6j)

Is there any way to know the exact rule of converting a complex number to its string representation as it appears in the Python interactive interpreter? Like some,

  1. In eg1 it prints 0j but in eg3 it does not.

  2. sometimes it prints 0 , -0 like in eg1 and eg4

  3. eg2 and eg5.

I know it doesn't make difference, but I want to implement the exactly same functionality as Python. I mean the output should be exactly the same.

Any help would be much appreciated. And it is not a homework problem, also thanks for reading.


Solution

  • Python will always display the imaginary part of a complex number, because otherwise you could not distinguish from the output that it is indeed complex. This is similar to including the .0 on a floating point number even when it is exactly an integer.

    The real part is displayed when it is not exactly +0. In the example you give the real part is being displayed because it is negative zero. Note that in floating point negative and positive zero have different representations even though they compare equal:

    >>> float.fromhex('-0x0.0')
    -0.0
    >>> float.fromhex('0x0.0')
    0.0
    >>> float.fromhex('-0x0.0') == 0.0
    True
    

    This may not help much in emulating Python's output as you probably can't easily predict when Python will get a negative 0 in its result. For example:

    >>> neg0 = float.fromhex('-0x0.0')
    >>> neg0
    -0.0
    >>> neg0+0j
    0j
    

    The addition has normalised the real part to +0

    >>> neg0*1j
    (-0+0j)
    >>> 0.0*(-1j)
    -0j
    

    multiplying by a positive imaginary keeps the negative sign on the real part, but multiplying by a negative imaginary doesn't make the real part negative.

    But all of this is just implementation detail so can be different with different implementations of Python.

    The fact that floating point has two separate representations for positive and negative zero is a detail of the IEEE 754 implementation of floating point. See https://en.wikipedia.org/wiki/Signed_zero

    The part of the source code which detects whether or not to output the real part includes:

     if (v->cval.real == 0. && copysign(1.0, v->cval.real)==1.0) {
        /* Real part is +0: just output the imaginary part and do not
           include parens. */
    

    The first part is true for both forms of zero. The second part is true only if the real part is not negative (copysign(x,y) gives the value of x with the sign of y, so copysign(1.0, negativezero) would be -1.