Shaking off the RustRust Mascot

Complex Numbers

Febuary 27th, 2022

Thumbnail for Complex Numbers
Difficulty: Beginner

Shaking off the Rust is a series of exercises with the Rust programing language. The purpose of the series is to improve both my and my dear reader's abilities with Rust by building things. Plus, by actually building stuff, we'll learn about an array of technological concepts in the process. In this installment, we're going to dig into complex numbers.

This installment's Github repo: https://github.com/josht-jpg/rust-complex-numbers

Intro to Complex Numbers (feel free to skip if you’re already familiar)


Complex numbers are simple. They have the form a+bia + bi, where aa and bb are real numbers, and ii, called the imaginary unit, has the property i2=1i^2 = -1 [1]. We call aa the real part of the complex number and bb the imaginary part. Real numbers and complex numbers follow the same laws of arithmetic [2]. Let’s explore complex numbers, and learn some stuff about Rust along the way.

Getting Started


As usual, we’ll begin by creating a new library with Cargo.

cargo new complex_numbers --lib
cd complex_numbers

Defining the Complex Tuple Struct


I’m going to use a tuple struct to represent Complex numbers. Tuple structs are structs that do not have names for their fields. Tuple structs are useful when you want to give a name to the tuple, but naming each field would be verbose [3].

Here’s what our Complex tuple struct looks like:

// lib.rs

#[derive(Debug, PartialEq, Clone, Copy)]
pub struct Complex(pub f64, pub f64);

The first number in the Complex tuple struct represents the real part of the complex number and the second number represents the imaginary part.

I’ve talked about each of those derived traits in the DNA Analysis installment of Shaking off the Rust, so I won’t repeat myself here.

If the traits being derived are unfamiliar to you, I urge you to give some of the Rust documentation a quick read:

These are common traits that you will see many times while working with Rust.

Adding Complex Numbers


You can probably guess how adding complex numbers works. For any two complex numbers

a+bi,c+dia + bi, c + di, we have that (a+bi)+(c+di)=(a+c)+(b+d)i(a + bi) + (c + di) = (a + c) + (b + d)i [2].

Let’s try it in Rust:

pub fn add(z1: &Complex, z2: &Complex) -> Complex {
    let Complex(a, b) = z1;
    let Complex(c, d) = z2;

    Complex(a + c, b + d)
}

If this is your first time seeing a tuple struct being destructured, the syntax may be confusing

let Complex(a, b) = z1;

By the end of this session of SOTR, that syntax will be second nature to you.

Let’s give the add function a short test. We’ll see how it adds (2.5+4i)(-2.5 + 4i) and (5.5+1.5i)(5.5 + 1.5i) together (we should get back (3+5.5i)(3 + 5.5i)).

// lib.rs

/*...*/

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn add_test() {
        let z1 = Complex(-2.5, 4.);
        let z2 = Complex(5.5, 1.5);
        assert_eq!(add(&z1, &z2), Complex(3., 5.5));
    }
}

As you can see, we instantiate tuple structs with round brackets: let z1 = Complex(-2.5, 4.);, rather than the curly braces we use for regular structs.

Multiplying Complex Numbers


Multiplying complex numbers is also straight forward:

(a+bi)(c+di)=ac+adi+cbibd=(acbd)+(ad+cb)i(a + bi)(c + di) = ac + adi + cbi - bd = (ac - bd) + (ad + cb)i

which follows from the relation i2=1i^2 = -1. Cool.

Again, trying that in Rust:

pub fn mult(z1: &Complex, z2: &Complex) -> Complex {
    let Complex(a, b) = z1;
    let Complex(c, d) = z2;

    Complex(a * c - b * d, a * d + b * c)
}

Cool cool cool. Not too hard.

Testing this with (2+4i)(-2 + 4i) and (2.5+0.5i)(2.5 + 0.5i), we looking for a result of (7+9i)(-7 + 9i):

#[cfg(test)]
mod tests {

    /*...*/

    #[test]
    fn mult_test() {
        let z1 = Complex(-2., 4.);
        let z2 = Complex(2.5, 0.5);
        assert_eq!(mult(&z1, &z2), Complex(-7., 9.));
    }
}

Dividing Complex Numbers


Dividing complex numbers is more complex than the previous two operations.

Let’s say we want to find the result of a+bic+di\frac{a+bi}{c+di} (here we are assuming c+dic + di is not equal to 00). Let x+yix + yi be the quotient: a+bic+di=x+yi\frac{a+bi}{c+di} = x+yi.

So a+bi=(c+di)(x+yi)a+bi = (c+di)(x+yi), which can be rewritten as a+bi=(cxdy)+(cy+dx)ia+bi = (cx - dy) + (cy + dx)i .

Thus, we have the system of equations: a=cddy,b=cy+dxa = cd - dy, b = cy + dx.

The unique solution to this system is: a=ac+bdc2+d2,b=bcadc2+d2a = \frac{ac + bd}{c^2 + d^2}, b = \frac{bc - ad}{c^2 + d^2} [2].

Congrats if you got through that. Let’s see what a Rust implementation looks like.

pub fn div(z1: &Complex, z2: &Complex) -> Complex {
    let Complex(a, b) = z1;
    let Complex(c, d) = z2;

    Complex(
        (a * c + b * d) / (c.powi(2) + d.powi(2)),
        (b * c - a * d) / (c.powi(2) + d.powi(2)),
    )
}

If this is the first time you’ve seen Rust’s powi method, note that it raises numbers to an integer power [4]. You could probably guess that from reading the code - I bring it up to point out there also exists a powf method, which raises numbers to a floating-point number (but is generally slower than powi).

As per usual, we’ll run a test. Using a dividend of (8+4i)(8 + 4i) and a divisor of (33i)(3 -3i), we expect our program to return (23+2i)(\frac{2}{3} + 2i).

#[cfg(test)]
mod tests {

    /*...*/

    #[test]
    fn div_test() {
        let z1 = Complex(8., 4.);
        let z2 = Complex(3., -3.);

        assert_eq!(div(&z1, &z2), Complex(2. / 3., 2.))
    }
}
		

Magnitude


There’s something I haven’t told you. This installment of SOTR is a setup for next week’s. Here’s a hint on what we’ll be doing:

We’ll need one more operation on complex numbers for next week: the magnitude.

The magnitude of a complex number z=a+biz = a+bi is z=a2+b2|z| = \sqrt{a^2 + b^2}.

This is easy to implement in Rust:

pub fn magnitude(z: &Complex) -> f64 {
    let Complex(a, b) = z;

    (a.powi(2) + b.powi(2)).sqrt()
}

And we’ll finish off with a test:

#[cfg(test)]
mod tests {
    
	  /*...*/

    #[test]
    fn magnitude_test() {
        let z = Complex(-3., 4.);
        assert_eq!(magnitude(&z), 5.);
    }
}

If that passed for you then congratulations! You’ve completed this week’s exercises and are ready for next week’s admittedly more exciting installment.

Thank you for coding with me, friends. I hope this post has left you one step closer to becoming the best programmer you can be.

References


[1] Needham, T. (2009). Visual Complex Analysis. Oxford University Press.

[2] Ahlfors, L. (1953). Complex Analysis: An Introduction to The Theory of Analytic Functions of One Complex Variable. McGraw-Hill Education.

[3] Nichols, C. and Klabnik, S. (2018). The Rust Programming Language. No Starch Press.

[4] Rust Documation on powi


Support Me

Creating and running Shaking off the Rust is one of the most fulfilling things I do. But it's exhausting. By supporting me, even if it's just a dollar, you'll allow me to put more time into building this series. I really appreciate any support.

The only way to support me right now is by sponsoring me on Github. I'll probably also set up Patreon and Donorbox pages soon.

Thank you so much!

Rust up your inbox!

Subscribe

No spam. Unsubscribe anytime.