Complex Numbers in Python

Complex numbers in python

In python, you can put ‘j’ or ‘J’ after a number to make it imaginary, so you can write complex literals easily:

>>> 1j
1j
>>> 1J
1j
>>> 1j * 1j
(-1+0j)

The ‘j’ suffix comes from electrical engineering, where the variable ‘i’ is usually used for current. (Reasoning found here.)

The type of a complex number is complex, and you can use the type as a constructor if you prefer:

>>> complex(2,3)
(2+3j)

A complex number has some built-in accessors:

>>> z = 2+3j
>>> z.real
2.0
>>> z.imag
3.0
>>> z.conjugate()
(2-3j)

Several built-in functions support complex numbers:

>>> abs(3 + 4j)
5.0
>>> pow(3 + 4j, 2)
(-7+24j)

The standard module cmath has more functions that handle complex numbers:

>>> import cmath
>>> cmath.sin(2 + 3j)
(9.15449914691143-4.168906959966565j)

Representing complex numbers in python

The j is like the decimal point or the e in floating point exponent notation: It's part of the notation for the number literal itself, not some operator you can tack on like a minus sign.

If you want to multiply x by 1j, you have to use the multiplication operator. That's x * 1j.

The j by itself is an identifier like x is. It's not number notation if it doesn't start with a dot or digit. But you could assign it a value, like j = 1j, and then x * j would make sense and work.

Similarly, xj is not implicit multiplication of x and j, but a separate identifier word spelled with two characters. You can use it as a variable name and assign it a separate value, just like the names x, j and foo.

Why is Python creating a complex number here?

your not taking into account the negative at the start. what python will actually interpret

-0.021678371395529073 ** 0.09090909090909091 as is really -(0.021678371395529073 ** 0.09090909090909091)

So do the power with 2 positive numbers then apply the negative. However if you wrap the first number and negative together to python knows the negative applies to that first number not just the net result of the expression you get the complex number

(-0.021678371395529073) ** 0.09090909090909091
(0.6772850578932906+0.1988688362687656j)

this is what happens in your real example as python knows the term is negative, not the result

To explain why this is happening you have to look at the order of operations. In your expression you have two operations - and ** and you have two terms 0.021678371395529073, 0.09090909090909091

below table lays out the order of operations, those that appear higher in the table are done first.

Operator

Description
() Parentheses (grouping)
f(args...) Function call
x[index:index] Slicing
x[index] Subscription
x.attribute Attribute reference
** Exponentiation
~x Bitwise not
+x, -x Positive, negative
*, /, % Multiplication, division, remainder
+, - Addition, subtraction
<<, >> Bitwise shifts
& Bitwise AND
^ Bitwise XOR
| Bitwise OR
in, not in, is, is not, <, <=, >, >=,
<>, !=, == Comparisons, membership, identity
not x Boolean NOT
and Boolean AND
or Boolean OR
lambda Lambda expression

from this table you can see that Exponentiation(**) has a higher precedent than Negation (-x). So that means python will first raise to the power of the two terms, then apply the negation to the result of that. So if you want the negation to be applied to the first number before raising to the power, then you need to wrap that in Parentheses.

Creating class for Complex Number in Python

Each of your operator implementations is mutating the attributes of its left operand. So after you execute x + y, x becomes the sum of x and y. Then when you execute x - y, x becomes the difference of x and y. After x+y, x-y, x*y, x/y all finish running, x has returned to its original value, since subtraction cancels out addition and division cancels out multiplication. Then the print function executes and displays your x object four times.

Operator implementations should return a new instance of the class rather than modifying the existing one.

import math

class Complex(object):
def __init__(self, real, imaginary):
self.real = real
self.imaginary = imaginary

def __add__(self, no):
return Complex(self.real + no.real, self.imaginary + no.imaginary)

def __sub__(self, no):
return Complex(self.real - no.real, self.imaginary - no.imaginary)

def __mul__(self, no):
return Complex(self.real * no.real, self.imaginary * no.imaginary)

def __truediv__(self, no):
return Complex(self.real / no.real, self.imaginary / no.imaginary)

def mod(self):
return Complex(math.sqrt(self.real**2 + self.imaginary**2), 0)

def __str__(self):
if self.imaginary == 0:
result = "%.2f+0.00i" % (self.real)
elif self.real == 0:
if self.imaginary >= 0:
result = "0.00+%.2fi" % (self.imaginary)
else:
result = "0.00-%.2fi" % (abs(self.imaginary))
elif self.imaginary > 0:
result = "%.2f+%.2fi" % (self.real, self.imaginary)
else:
result = "%.2f-%.2fi" % (self.real, abs(self.imaginary))
return result

if __name__ == '__main__':
x = Complex(2,1)
y = Complex(5,6)
print(*map(str, [x+y, x-y, x*y, x/y, x.mod(), y.mod()]), sep='\n')

Result:

7.00+7.00i
-3.00-5.00i
10.00+6.00i
0.40+0.17i
2.24+0.00i
7.81+0.00i

I notice that even with these changes, multiplication and division do not provide the expected result. This is because you can't multiply or divide a complex number just by performing arithmetic on its real and imaginary components separately. 2i * 3i is not 6i, for example; it is -6+0i.

Try these implementations:

def __mul__(self, no):
return Complex(self.real * no.real - self.imaginary * no.imaginary, self.real * no.imaginary + no.real * self.imaginary)

def __truediv__(self, no):
denominator = no.real**2 + no.imaginary**2
return Complex((self.real*no.real + self.imaginary*no.imaginary) / denominator, (self.imaginary*no.real - self.real*no.imaginary) / denominator)

Now the output for multiplication and division will be 4.00+17.00i and
0.26-0.11i.

Can a complex number be unpacked into x and y in python?

One thing you can do is subclass complex and implement a custom __iter__ method that returns the unpacked version you seek.

I chose to call it complez in this case, since z is often a symbol for a complex number.

import cmath

class complez(complex):
def __iter__(self):
return iter((self.real, self.imag))

z = complez(3, 5)
x, y = z
print(x, y)
# 3.0 5.0

You can also do the following, if you don't mind overriding the existing complex. Using this approach allows you to continue using the complex class throughout your code without needing to make any changes to naming.

import builtins
import cmath

class complex(builtins.complex):
def __iter__(self):
return iter((self.real, self.imag))

z = complex(3, 5)
x, y = z
print(x, y)
# 3.0 5.0

(Technically, you could also drop the use of the builtins module).

In either case, the standard unpacking operator would also work, e.g.

print(*z)
# 3.0 5.0

NOTE: This solves your immediate problem, but it does have consequences. When you perform operations on these new numbers, e.g. adding 2 complez numbers, you would get a complex number result -- which won't have the convenient __iter__ defined, so you'd have to override other methods to account for this side-effect. For example:

def __add__(self, other):
return complez(super().__add__(other))

Hence, this approach does result in more overhead in producing a fully general solution. But, this is the tradeoff for having the convenience of natural unpacking.



Related Topics



Leave a reply



Submit