Avatar of paulfioravanti

paulfioravanti's solution

to Complex Numbers in the Ruby Track

Published at May 12 2019 · 0 comments
Instructions
Test suite
Solution

A complex number is a number in the form a + b * i where a and b are real and i satisfies i^2 = -1.

a is called the real part and b is called the imaginary part of z. The conjugate of the number a + b * i is the number a - b * i. The absolute value of a complex number z = a + b * i is a real number |z| = sqrt(a^2 + b^2). The square of the absolute value |z|^2 is the result of multiplication of z by its complex conjugate.

The sum/difference of two complex numbers involves adding/subtracting their real and imaginary parts separately: (a + i * b) + (c + i * d) = (a + c) + (b + d) * i, (a + i * b) - (c + i * d) = (a - c) + (b - d) * i.

Multiplication result is by definition (a + i * b) * (c + i * d) = (a * c - b * d) + (b * c + a * d) * i.

The reciprocal of a non-zero complex number is 1 / (a + i * b) = a/(a^2 + b^2) - b/(a^2 + b^2) * i.

Dividing a complex number a + i * b by another c + i * d gives: (a + i * b) / (c + i * d) = (a * c + b * d)/(c^2 + d^2) + (b * c - a * d)/(c^2 + d^2) * i.

Raising e to a complex exponent can be expressed as e^(a + i * b) = e^a * e^(i * b), the last term of which is given by Euler's formula e^(i * b) = cos(b) + i * sin(b).

Implement the following operations:

  • addition, subtraction, multiplication and division of two complex numbers,
  • conjugate, absolute value, exponent of a given complex number.

Assume the programming language you are using does not have an implementation of complex numbers.


For installation and learning resources, refer to the Ruby resources page.

For running the tests provided, you will need the Minitest gem. Open a terminal window and run the following command to install minitest:

gem install minitest

If you would like color output, you can require 'minitest/pride' in the test file, or note the alternative instruction, below, for running the test file.

Run the tests from the exercise directory using the following command:

ruby complex_numbers_test.rb

To include color from the command line:

ruby -r minitest/pride complex_numbers_test.rb

Source

Wikipedia https://en.wikipedia.org/wiki/Complex_number

Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

complex_numbers_test.rb

require 'minitest/autorun'
require_relative 'complex_numbers'

# Common test data version: 1.3.0 1e1c9ca
class ComplexNumbersTest < Minitest::Test
  def test_real_part_of_a_purely_real_number
    # skip
    expected = 1
    assert_equal expected, ComplexNumber.new(1, 0).real
  end

  def test_real_part_of_a_purely_imaginary_number
    skip
    expected = 0
    assert_equal expected, ComplexNumber.new(0, 1).real
  end

  def test_real_part_of_a_number_with_real_and_imaginary_part
    skip
    expected = 1
    assert_equal expected, ComplexNumber.new(1, 2).real
  end

  def test_imaginary_part_of_a_purely_real_number
    skip
    expected = 0
    assert_equal expected, ComplexNumber.new(1, 0).imaginary
  end

  def test_imaginary_part_of_a_purely_imaginary_number
    skip
    expected = 1
    assert_equal expected, ComplexNumber.new(0, 1).imaginary
  end

  def test_imaginary_part_of_a_number_with_real_and_imaginary_part
    skip
    expected = 2
    assert_equal expected, ComplexNumber.new(1, 2).imaginary
  end

  def test_imaginary_unit
    skip
    expected = ComplexNumber.new(-1, 0)
    assert_equal expected, ComplexNumber.new(0, 1) * ComplexNumber.new(0, 1)
  end

  def test_add_purely_real_numbers
    skip
    expected = ComplexNumber.new(3, 0)
    assert_equal expected, ComplexNumber.new(1, 0) + ComplexNumber.new(2, 0)
  end

  def test_add_purely_imaginary_numbers
    skip
    expected = ComplexNumber.new(0, 3)
    assert_equal expected, ComplexNumber.new(0, 1) + ComplexNumber.new(0, 2)
  end

  def test_add_numbers_with_real_and_imaginary_part
    skip
    expected = ComplexNumber.new(4, 6)
    assert_equal expected, ComplexNumber.new(1, 2) + ComplexNumber.new(3, 4)
  end

  def test_subtract_purely_real_numbers
    skip
    expected = ComplexNumber.new(-1, 0)
    assert_equal expected, ComplexNumber.new(1, 0) - ComplexNumber.new(2, 0)
  end

  def test_subtract_purely_imaginary_numbers
    skip
    expected = ComplexNumber.new(0, -1)
    assert_equal expected, ComplexNumber.new(0, 1) - ComplexNumber.new(0, 2)
  end

  def test_subtract_numbers_with_real_and_imaginary_part
    skip
    expected = ComplexNumber.new(-2, -2)
    assert_equal expected, ComplexNumber.new(1, 2) - ComplexNumber.new(3, 4)
  end

  def test_multiply_purely_real_numbers
    skip
    expected = ComplexNumber.new(2, 0)
    assert_equal expected, ComplexNumber.new(1, 0) * ComplexNumber.new(2, 0)
  end

  def test_multiply_purely_imaginary_numbers
    skip
    expected = ComplexNumber.new(-2, 0)
    assert_equal expected, ComplexNumber.new(0, 1) * ComplexNumber.new(0, 2)
  end

  def test_multiply_numbers_with_real_and_imaginary_part
    skip
    expected = ComplexNumber.new(-5, 10)
    assert_equal expected, ComplexNumber.new(1, 2) * ComplexNumber.new(3, 4)
  end

  def test_divide_purely_real_numbers
    skip
    expected = ComplexNumber.new(0.5, 0)
    assert_equal expected, ComplexNumber.new(1, 0) / ComplexNumber.new(2, 0)
  end

  def test_divide_purely_imaginary_numbers
    skip
    expected = ComplexNumber.new(0.5, 0)
    assert_equal expected, ComplexNumber.new(0, 1) / ComplexNumber.new(0, 2)
  end

  def test_divide_numbers_with_real_and_imaginary_part
    skip
    expected = ComplexNumber.new(0.44, 0.08)
    assert_equal expected, ComplexNumber.new(1, 2) / ComplexNumber.new(3, 4)
  end

  def test_absolute_value_of_a_positive_purely_real_number
    skip
    expected = 5
    assert_equal expected, ComplexNumber.new(5, 0).abs
  end

  def test_absolute_value_of_a_negative_purely_real_number
    skip
    expected = 5
    assert_equal expected, ComplexNumber.new(-5, 0).abs
  end

  def test_absolute_value_of_a_purely_imaginary_number_with_positive_imaginary_part
    skip
    expected = 5
    assert_equal expected, ComplexNumber.new(0, 5).abs
  end

  def test_absolute_value_of_a_purely_imaginary_number_with_negative_imaginary_part
    skip
    expected = 5
    assert_equal expected, ComplexNumber.new(0, -5).abs
  end

  def test_absolute_value_of_a_number_with_real_and_imaginary_part
    skip
    expected = 5
    assert_equal expected, ComplexNumber.new(3, 4).abs
  end

  def test_conjugate_a_purely_real_number
    skip
    expected = ComplexNumber.new(5, 0)
    assert_equal expected, ComplexNumber.new(5, 0).conjugate
  end

  def test_conjugate_a_purely_imaginary_number
    skip
    expected = ComplexNumber.new(0, -5)
    assert_equal expected, ComplexNumber.new(0, 5).conjugate
  end

  def test_conjugate_a_number_with_real_and_imaginary_part
    skip
    expected = ComplexNumber.new(1, -1)
    assert_equal expected, ComplexNumber.new(1, 1).conjugate
  end

  def test_eulers_identity_formula
    skip
    expected = ComplexNumber.new(-1, 0)
    assert_equal expected, ComplexNumber.new(0, Math::PI).exp
  end

  def test_exponential_of_0
    skip
    expected = ComplexNumber.new(1, 0)
    assert_equal expected, ComplexNumber.new(0, 0).exp
  end

  def test_exponential_of_a_purely_real_number
    skip
    expected = ComplexNumber.new(Math::E, 0)
    assert_equal expected, ComplexNumber.new(1, 0).exp
  end

  def test_exponential_of_a_number_with_real_and_imaginary_part
    skip
    expected = ComplexNumber.new(-2, 0)
    assert_equal expected, ComplexNumber.new(Math.log(2), Math::PI).exp
  end
end
class ComplexNumber
  ADD_POWERS_OF_TWO = lambda do |complex_number|
    complex_number.real**2 + complex_number.imaginary**2
  end
  private_constant :ADD_POWERS_OF_TWO

  attr_reader :real, :imaginary

  def initialize(real, imaginary)
    @real = real
    @imaginary = imaginary
  end

  def ==(other)
    real == other.real && imaginary == other.imaginary
  end

  # (a + i * b) + (c + i * d) = (a + c) + (b + d) * i
  def +(other)
    self.class.new(
      add_real(other),
      add_imaginary(other)
    )
  end

  # (a + i * b) - (c + i * d) = (a - c) + (b - d) * i
  def -(other)
    self.class.new(
      subtract_real(other),
      subtract_imaginary(other)
    )
  end

  # (a + i * b) * (c + i * d) = (a * c - b * d) + (b * c + a * d) * i
  def *(other)
    self.class.new(
      multiplication_real(other),
      multiplication_imaginary(other)
    )
  end

  # (a + i * b) / (c + i * d) =
  #   (a * c + b * d) / (c^2 + d^2) + (b * c - a * d) / (c^2 + d^2) * i
  def /(other)
    self.class.new(
      divide_real(other),
      divide_imaginary(other)
    )
  end

  # |z| = sqrt(a^2 + b^2)
  def abs
    self
      .then(&ADD_POWERS_OF_TWO)
      .then(&Math.method(:sqrt))
  end

  # a - b * i
  def conjugate
    self.class.new(real, -imaginary)
  end

  # e^(a + i * b) = e^a * e^(i * b)
  # => e^(i * b) = cos(b) + i * sin(b)
  def exp
    self.class.new(
      Math.exp(real) * eulers_formula,
      0
    )
  end

  private

  # (a + c)
  def add_real(other)
    real + other.real
  end

  # (b + d)
  def add_imaginary(other)
    imaginary + other.imaginary
  end

  # (a - c)
  def subtract_real(other)
    real - other.real
  end

  # (b - d)
  def subtract_imaginary(other)
    imaginary - other.imaginary
  end

  # (a * c - b * d)
  def multiplication_real(other)
    multiply_real(other) - multiply_imaginary(other)
  end

  # (b * c + a * d)
  def multiplication_imaginary(other)
    multiply_imaginary_to_real(other) + multiply_real_to_imaginary(other)
  end

  # (a * c)
  def multiply_real(other)
    real * other.real
  end

  # (a * d)
  def multiply_real_to_imaginary(other)
    real * other.imaginary
  end

  # (b * d)
  def multiply_imaginary(other)
    imaginary * other.imaginary
  end

  # (b * c)
  def multiply_imaginary_to_real(other)
    imaginary * other.real
  end

  # (a * c + b * d) / (c^2 + d^2)
  def divide_real(other)
    (multiply_real(other) + multiply_imaginary(other)) /
      division_denominator(other)
  end

  # (b * c - a * d) / (c^2 + d^2)
  def divide_imaginary(other)
    (multiply_imaginary_to_real(other) - multiply_real_to_imaginary(other)) /
      division_denominator(other)
  end

  # (c^2 + d^2)
  def division_denominator(other)
    other
      .then(&ADD_POWERS_OF_TWO)
      .to_f
  end

  # e^(i * b) = cos(b) + i * sin(b)
  def eulers_formula
    (Math.cos(imaginary) + Math.sin(imaginary)).round
  end
end

Community comments

Find this solution interesting? Ask the author a question to learn more.

What can you learn from this solution?

A huge amount can be learned from reading other people’s code. This is why we wanted to give exercism users the option of making their solutions public.

Here are some questions to help you reflect on this solution and learn the most from it.

  • What compromises have been made?
  • Are there new concepts here that you could read more about to improve your understanding?