# Complex numbers (Ruby)

Other implementations: C++ | Java | Ruby | Scala

## Representation

We will represent a complex number with the `Complex` class. This class includes initialization and accessors for to query and build the complex number as well as basic arithmetic functions:

```<<complex class>>=
class Complex
initialization
accessors
arithmetic
end
```

We will provide access to both the coordinate and polar/phasor representations of the complex number.

### Initialization

The initialization method will store only the starting representation:

```<<initialization>>=
def initialize( a, b, type=:coordinate )
case type
when :coordinate
@real = a.to_f
@imag = b.to_f
when :polar
@magnitude = a.to_f
@theta = b.to_f
else
raise ArgumentError, "unknown type '#{type}'"
end
end
```

### Accessors

Access to a representation other than the original is provided on demand, with caching:

```<<accessors>>=
def real
@real ||= @magnitude * Math.cos(@theta)
end
def imag
@imag ||= @magnitude * Math.sin(@theta)
end
def magnitude
@magnitude ||= Math.hypot(@real, @imag)
end
def theta
@theta ||= Math.atan2(@imag, @real)
end
```

## Arithmetic

Core arithmetic operations provided are addition, subtraction, multiplication, division and exponentiation. The arithmetic interface is unified to accept either complex or real arguments, with any necessary distinction made in the method itself.

```<<arithmetic>>=
subtraction
multiplication
division
exponentiation
```

Addition and subtraction are implemented using the coordinate representation:

```<<addition>>=
def +( other )
if Complex === other
Complex.new(
self.real + other.real,
self.imag + other.imag
)
else
Complex.new(
self.real + other,
self.imag
)
end
end
<<subtraction>>=
def -( other )
if Complex === other
Complex.new(
self.real - other.real,
self.imag - other.imag
)
else
Complex.new(
self.real - other,
self.imag
)
end
end
```

### Multiplication and division

Multiplication and division are implemented using the polar/phasor representation:

```<<multiplication>>=
def *( other )
if Complex === other
Complex.new(
self.magnitude * other.magnitude,
self.theta + other.theta,
:polar
)
else
Complex.new(
self.magnitude * other,
self.theta,
:polar
)
end
end
<<division>>=
def /( other )
if Complex === other
Complex.new(
self.magnitude / other.magnitude,
self.theta - other.theta,
:polar
)
else
Complex.new(
self.magnitude / other,
self.theta,
:polar
)
end
end
```

### Exponentiation

Exponentiation with a real exponent is easy using the polar/phasor representation. To implement exponentiation with a complex exponent, we'll make use of a change in base:

qz = ezlnq

Letting and , we can derive:

qz = ea + ib
qz = eaeib
| qz | = ea
θ(qz) = b
```<<exponentiation>>=
def **( other )
if Complex === other
log = log
z_prime = other * log
Complex.new(
Math.exp(z_prime.real),
z_prime.imag,
:polar
)
else
Complex.new(
self.magnitude ** other,
self.theta * other,
:polar
)
end
end
```

To calculate the logarithm of q when q is complex, we utilize the polar/phasor representation:

lnq = lnreiθ = lnr + iθ
```<<log>>=
Complex.new(
Math.ln(self.magnitude),
self.theta,
:coordinate
)
```

## Test Suite

To build a test suite for this class we'll use the `Test::Unit` library. First we place the class in a file, then include that class and build a `TestCase`.

```<<complex.rb>>=
complex class
<<complex_test.rb>>=
require 'test/unit'
require './complex'
class TestComplex < Test::Unit::TestCase
define tolerance
test initialization from coordinates
test initialization from polar
test polar access from coordinates
test coordinate access from polar
test subtraction
test multiplication
test division
test real exponentiation
end
```

### Initialization

First we'll test that initialization works as expected:

```<<test initialization from coordinates>>=
def test_initialization_from_coordinates
z = Complex.new( 1, 2 )
assert_equal 1, z.real
assert_equal 2, z.imag
end
<<test initialization from polar>>=
def test_initialization_from_polar
z = Complex.new( 1, 2, :polar )
assert_equal 1, z.magnitude
assert_equal 2, z.theta
end
```

### Accessors

Next we verify that our on-demand accessors work correctly. These and the arithmetic checks all require that we define a tolerance first:

```<<define tolerance>>=
TOLERANCE = 0.001
<<test polar access from coordinates>>=
def test_polar_access_from_coordinates
z = Complex.new( 1, 1 )
assert_in_delta Math.sqrt(2), z.magnitude, TOLERANCE
assert_in_delta Math::PI/4, z.theta, TOLERANCE
end
<<test coordinate access from polar>>=
def test_coordinates_access_from_polar
z = Complex.new( 1, Math::PI / 4, :polar )
assert_in_delta Math.sqrt(0.5), z.real, TOLERANCE
assert_in_delta Math.sqrt(0.5), z.imag, TOLERANCE
end
```

### Arithmetic

Finally we'll check all of our arithmetic methods:

```<<test addition>>=
z1 = Complex.new( 1, 2 )
z2 = Complex.new( 3, 4 )
z3 = z1 + z2
assert_equal 4, z3.real
assert_equal 6, z3.imag
z4 = z1 + 5
assert_equal 6, z4.real
assert_equal 2, z4.imag
end
<<test subtraction>>=
def test_subtraction
z1 = Complex.new( 3, 4 )
z2 = Complex.new( 2, 1 )
z3 = z1 - z2
assert_equal 1, z3.real
assert_equal 3, z3.imag
z4 = z1 - 2
assert_equal 1, z4.real
assert_equal 4, z4.imag
end
<<test multiplication>>=
def test_multiplication
z1 = Complex.new( 1, Math::PI / 3, :polar )
z2 = Complex.new( 2, Math::PI / 6, :polar )
z3 = z1 * z2
assert_equal 2, z3.magnitude
assert_in_delta Math::PI / 2, z3.theta, TOLERANCE
z4 = z1 * 5
assert_equal 5, z4.magnitude
assert_equal Math::PI / 3, z4.theta
end
<<test division>>=
def test_division
z1 = Complex.new( 1, Math::PI / 3, :polar )
z2 = Complex.new( 2, Math::PI / 4, :polar )
z3 = z1 / z2
assert_in_delta 0.5, z3.magnitude, TOLERANCE
assert_in_delta Math::PI / 12, z3.theta, TOLERANCE
z4 = z1 / 3
assert_in_delta 1.0/3.0, z4.magnitude, TOLERANCE
assert_equal Math::PI / 3, z4.theta
end
<<test real exponentiation>>=
def test_exponentiation
z1 = Complex.new( 2, Math::PI / 3, :polar )
z2 = z1 ** 3
assert_equal 8, z2.magnitude
assert_in_delta Math::PI, z2.theta, TOLERANCE
z3 = z1 ** 0.5
assert_in_delta Math.sqrt(2), z3.magnitude, TOLERANCE
assert_in_delta Math::PI / 6, z3.theta, TOLERANCE
end
```