Skip to content

Commit

Permalink
fix: non-finite numbers in various functions (#28)
Browse files Browse the repository at this point in the history
* New test cases

* Multiply/2 test cases

* Divide/2 tests

* Some pow/2 tests

* Add remaining pow/2 tests

* atan2 tests and fix

* Fix for add/2

* Subtract/2 fix

* Fix multiply/2

* Fix divide/2

* Fix atan2/2

* Fix multiply/2 doctest

* fix: make tests pass

* fix: ci

* fix: elixir version ci

---------

Co-authored-by: Paulo Valente <[email protected]>
  • Loading branch information
hunterboerner and polvalente authored Jan 9, 2025
1 parent dcdc7f5 commit 1d1e6e8
Show file tree
Hide file tree
Showing 3 changed files with 376 additions and 69 deletions.
12 changes: 6 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ jobs:
strategy:
fail-fast: false
matrix:
elixir: ["1.12.0"]
otp: ["24.0"]
elixir: ["1.18.0"]
otp: ["27.0"]
env:
MIX_ENV: test
steps:
Expand All @@ -22,7 +22,7 @@ jobs:
otp-version: ${{ matrix.otp }}
elixir-version: ${{ matrix.elixir }}
- name: Retrieve dependencies cache
uses: actions/cache@v1
uses: actions/cache@v4
id: mix-cache # id to use in retrieve action
with:
path: deps
Expand All @@ -42,8 +42,8 @@ jobs:
strategy:
fail-fast: false
matrix:
elixir: ["1.12.0"]
otp: ["24.0"]
elixir: ["1.18.0"]
otp: ["27.0"]
env:
MIX_ENV: test
steps:
Expand All @@ -57,7 +57,7 @@ jobs:
otp-version: ${{ matrix.otp }}
elixir-version: ${{ matrix.elixir }}
- name: Retrieve dependencies cache
uses: actions/cache@v1
uses: actions/cache@v4
id: mix-cache # id to use in retrieve action
with:
path: deps
Expand Down
189 changes: 140 additions & 49 deletions lib/complex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,12 @@ defmodule Complex do
@spec add(t | number | non_finite_number, t | number | non_finite_number) ::
t | number | non_finite_number

def add(z, %Complex{re: re, im: im}) when z in [:infinity, :neg_infinity, :nan],
do: Complex.new(add(z, re), im)

def add(%Complex{re: re, im: im}, z) when z in [:infinity, :neg_infinity, :nan],
do: Complex.new(add(z, re), im)

def add(:nan, _), do: :nan
def add(_, :nan), do: :nan

Expand Down Expand Up @@ -431,6 +437,12 @@ defmodule Complex do
@spec subtract(t | number | non_finite_number, t | number | non_finite_number) ::
t | number | non_finite_number

def subtract(z, %Complex{re: re, im: im}) when z in [:infinity, :neg_infinity, :nan],
do: Complex.new(subtract(z, re), negate(im))

def subtract(%Complex{re: re, im: im}, z) when z in [:infinity, :neg_infinity, :nan],
do: Complex.new(subtract(re, z), im)

def subtract(:nan, _), do: :nan
def subtract(_, :nan), do: :nan
def subtract(:infinity, :infinity), do: :nan
Expand Down Expand Up @@ -472,12 +484,22 @@ defmodule Complex do
%Complex{im: 6.0, re: 3.0}
iex> Complex.multiply(-2, Complex.new(:infinity, :neg_infinity))
%Complex{im: :infinity, re: :neg_infinity}
%Complex{im: :nan, re: :nan}
"""
@spec multiply(t | number | non_finite_number, t | number | non_finite_number) ::
t | number | non_finite_number

def multiply(x, c = %Complex{re: _re, im: _im}) when is_non_finite_number(x) or is_number(x) do
z = new(x, 0)
multiply(z, c)
end

def multiply(c = %Complex{re: _re, im: _im}, x) when is_non_finite_number(x) or is_number(x) do
z = new(x, 0)
multiply(c, z)
end

def multiply(:infinity, right) when is_number(right) and right > 0, do: :infinity
def multiply(:infinity, right) when right == 0, do: :nan
def multiply(:infinity, right) when is_number(right) and right < 0, do: :neg_infinity
Expand All @@ -500,28 +522,6 @@ defmodule Complex do
def multiply(:infinity, :neg_infinity), do: :neg_infinity
def multiply(:infinity, :infinity), do: :infinity

def multiply(x, %Complex{re: re, im: im}) when is_non_finite_number(x) or is_number(x) do
new(multiply(re, x), multiply(im, x))
end

def multiply(%Complex{re: re, im: im}, x) when is_non_finite_number(x) or is_number(x) do
new(multiply(re, x), multiply(im, x))
end

def multiply(%Complex{re: re_l, im: im_l}, %Complex{re: re_r, im: im_r}) when im_r == 0 do
new(multiply(re_r, re_l), multiply(re_r, im_l))
end

def multiply(%Complex{re: re_l, im: im_l}, %Complex{re: re_r, im: im_r}) when re_r == 0 do
re_result =
case multiply(im_l, im_r) do
result when result == 0 -> 0
result -> negate(result)
end

new(re_result, multiply(re_l, im_r))
end

def multiply(left, right) when is_number(left) and is_number(right), do: left * right

def multiply(left, right) do
Expand Down Expand Up @@ -569,33 +569,43 @@ defmodule Complex do
def divide(x, y) when is_non_finite_number(x) and is_non_finite_number(y), do: :nan
def divide(x, y) when is_non_finite_number(x) and is_number(y) and y >= 0, do: x
def divide(x, y) when x == 0 and y == 0, do: :nan
def divide(:nan, _), do: :nan
def divide(_, :nan), do: :nan
def divide(:nan, a) when is_number(a), do: :nan
def divide(a, :nan) when is_number(a), do: :nan
def divide(:infinity, y) when is_number(y) and y < 0, do: :neg_infinity
def divide(:neg_infinity, y) when is_number(y) and y < 0, do: :infinity
def divide(_, :infinity), do: 0
def divide(_, :neg_infinity), do: 0
def divide(a, :infinity) when is_number(a), do: 0
def divide(a, :neg_infinity) when is_number(a), do: 0

def divide(x, y) when is_number(x) and is_number(y), do: x / y

def divide(n, b) when is_number(n) and b in [:infinity, :neg_infinity] do
0
end

def divide(n, %Complex{re: re_r, im: im_r})
when is_number(n) and re_r in [:infinity, :neg_infinity] and im_r == 0 do
new(0, 0)
end

def divide(%Complex{re: re, im: im}, b)
when is_number(re) and is_number(im) and b in [:infinity, :neg_infinity] do
new(0, 0)
end

def divide(%Complex{re: re, im: im}, %Complex{re: re_r, im: im_r})
when is_number(re) and is_number(im) and re_r in [:infinity, :neg_infinity] and im_r == 0 do
new(0, 0)
end

def divide(x, y) do
%Complex{re: r1, im: i1} = as_complex(x)
%Complex{re: r2, im: i2} = as_complex(y)

cond do
i2 == 0 ->
new(divide(r1, r2), divide(i1, r2))

r2 == 0 ->
new(divide(i1, i2), negate(divide(r1, i2)))

true ->
num_re = add(multiply(r1, r2), multiply(i1, i2))
num_im = subtract(multiply(i1, r2), multiply(r1, i2))
den = add(square(r2), square(i2))
num_re = add(multiply(r1, r2), multiply(i1, i2))
num_im = subtract(multiply(i1, r2), multiply(r1, i2))
den = add(square(r2), square(i2))

new(divide(num_re, den), divide(num_im, den))
end
new(divide(num_re, den), divide(num_im, den))
end

@doc """
Expand Down Expand Up @@ -939,13 +949,49 @@ defmodule Complex do
def log10(z)

def log10(:infinity), do: :infinity
def log10(:neg_infinity), do: divide(log(:neg_infinity), :math.log(10))

def log10(:neg_infinity) do
new(:infinity, :math.pi() / :math.log(10))
end

def log10(:nan), do: :nan
def log10(n) when is_number(n) and n == 0, do: :neg_infinity
def log10(n) when is_number(n) and n < 0, do: :nan

def log10(n) when is_number(n), do: :math.log10(n)

def log10(%Complex{re: :infinity, im: im}) when is_number(im) do
new(:infinity, 0)
end

def log10(%Complex{re: :neg_infinity, im: im}) when is_number(im) do
new(:infinity, :math.pi() / :math.log(10))
end

def log10(%Complex{im: :infinity, re: re}) when is_number(re) do
new(:infinity, :math.pi() / (2 * :math.log(10)))
end

def log10(%Complex{im: :neg_infinity, re: re}) when is_number(re) do
new(:infinity, -:math.pi() / (2 * :math.log(10)))
end

def log10(%Complex{re: :infinity, im: :infinity}) do
new(:infinity, :math.pi() / (4 * :math.log(10)))
end

def log10(%Complex{re: :infinity, im: :neg_infinity}) do
new(:infinity, -:math.pi() / (4 * :math.log(10)))
end

def log10(%Complex{re: :neg_infinity, im: :neg_infinity}) do
new(:infinity, -3 * :math.pi() / (4 * :math.log(10)))
end

def log10(%Complex{re: :neg_infinity, im: :infinity}) do
new(:infinity, 3 * :math.pi() / (4 * :math.log(10)))
end

def log10(z = %Complex{}) do
divide(log(z), new(:math.log(10.0), 0.0))
end
Expand Down Expand Up @@ -975,6 +1021,38 @@ defmodule Complex do

def log2(n) when is_number(n), do: :math.log2(n)

def log2(%Complex{re: :infinity, im: im}) when is_number(im) do
new(:infinity, 0)
end

def log2(%Complex{re: :neg_infinity, im: im}) when is_number(im) do
new(:infinity, :math.pi() / :math.log(2))
end

def log2(%Complex{im: :infinity, re: re}) when is_number(re) do
new(:infinity, :math.pi() / (2 * :math.log(2)))
end

def log2(%Complex{im: :neg_infinity, re: re}) when is_number(re) do
new(:infinity, -:math.pi() / (2 * :math.log(2)))
end

def log2(%Complex{re: :infinity, im: :infinity}) do
new(:infinity, :math.pi() / (4 * :math.log(2)))
end

def log2(%Complex{re: :infinity, im: :neg_infinity}) do
new(:infinity, -:math.pi() / (4 * :math.log(2)))
end

def log2(%Complex{re: :neg_infinity, im: :neg_infinity}) do
new(:infinity, -3 * :math.pi() / (4 * :math.log(2)))
end

def log2(%Complex{re: :neg_infinity, im: :infinity}) do
new(:infinity, 3 * :math.pi() / (4 * :math.log(2)))
end

def log2(z = %Complex{}) do
divide(log(z), new(:math.log(2.0), 0.0))
end
Expand All @@ -996,6 +1074,18 @@ defmodule Complex do
@spec pow(t | non_finite_number | number, t | non_finite_number | number) ::
t | non_finite_number | number

def pow(%Complex{re: re, im: im}, :infinity) when re == 0 and im == 0, do: new(0, 0)

def pow(%Complex{re: re, im: im}, %Complex{re: :infinity, im: im_r})
when re == 0 and im == 0 and im_r == 0,
do: new(0, 0)

def pow(z, %Complex{}) when is_non_finite_number(z), do: new(:nan, :nan)
def pow(%Complex{}, z) when is_non_finite_number(z), do: new(:nan, :nan)
def pow(%Complex{re: z}, _) when is_non_finite_number(z), do: new(:nan, :nan)
def pow(%Complex{im: z}, _) when is_non_finite_number(z), do: new(:nan, :nan)
def pow(_, %Complex{re: z}) when is_non_finite_number(z), do: new(:nan, :nan)
def pow(_, %Complex{im: z}) when is_non_finite_number(z), do: new(:nan, :nan)
def pow(:nan, _), do: :nan
def pow(_, :nan), do: :nan

Expand All @@ -1015,9 +1105,8 @@ defmodule Complex do
def pow(:neg_infinity, y) when is_number(y) and y < 0, do: 0

def pow(x, :infinity) when x == 0, do: 0
def pow(%Complex{re: re, im: im}, :infinity) when re == 0 and im == 0, do: 0
def pow(x, :neg_infinity) when x == 0, do: :infinity
def pow(%Complex{re: re, im: im}, :neg_infinity) when re == 0 and im == 0, do: :infinity
def pow(%Complex{re: re, im: im}, :neg_infinity) when re == 0 and im == 0, do: new(:infinity, 0)
def pow(_, :neg_infinity), do: 0
def pow(_, :infinity), do: :infinity

Expand Down Expand Up @@ -1308,14 +1397,16 @@ defmodule Complex do
def atan2(:infinity, :infinity), do: :math.pi() / 4
def atan2(:infinity, :neg_infinity), do: 3 * :math.pi() / 4
def atan2(:infinity, :nan), do: :nan
def atan2(:infinity, _), do: :math.pi() / 2
def atan2(:infinity, a) when is_number(a), do: :math.pi() / 2
def atan2(:neg_infinity, :infinity), do: -:math.pi() / 4
def atan2(:neg_infinity, :neg_infinity), do: -3 * :math.pi() / 4
def atan2(:neg_infinity, :nan), do: :nan
def atan2(:neg_infinity, _), do: -:math.pi() / 2
def atan2(:nan, _), do: :nan
def atan2(_, :infinity), do: 0
def atan2(_, :neg_infinity), do: :math.pi()
def atan2(:neg_infinity, a) when is_number(a), do: -:math.pi() / 2
def atan2(:nan, a) when is_number(a) or is_non_finite_number(a), do: :nan
def atan2(a, :nan) when is_number(a) or is_non_finite_number(a), do: :nan
def atan2(:nan, :nan), do: :nan
def atan2(a, :infinity) when is_number(a), do: 0
def atan2(a, :neg_infinity) when is_number(a), do: :math.pi()

def atan2(b, a) do
a = as_complex(a)
Expand Down Expand Up @@ -1416,7 +1507,7 @@ defmodule Complex do
### Examples
iex> Complex.asec(Complex.from_polar(2,:math.pi))
%Complex{im: 0.0, re: 2.0943951023931957}
%Complex{im: -0.0, re: 2.0943951023931957}
iex> Complex.sec(Complex.asec(Complex.new(2,3)))
%Complex{im: 2.9999999999999982, re: 1.9999999999999987}
Expand Down
Loading

0 comments on commit 1d1e6e8

Please sign in to comment.