Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 88 additions & 12 deletions lib/complex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,15 @@ defmodule Complex do
def divide(a, :infinity) when is_number(a), do: 0
def divide(a, :neg_infinity) when is_number(a), do: 0

def divide(x, +0.0) when is_number(x) and x > 0, do: :infinity
def divide(x, +0.0) when is_number(x) and x < 0, do: :neg_infinity
def divide(x, +0.0) when is_number(x), do: :nan
def divide(x, -0.0) when is_number(x) and x > 0, do: :neg_infinity
def divide(x, -0.0) when is_number(x) and x < 0, do: :infinity
def divide(x, -0.0) when is_number(x), do: :nan
def divide(x, 0) when is_number(x) and x > 0, do: :infinity
def divide(x, 0) when is_number(x) and x < 0, do: :neg_infinity
def divide(x, 0) when is_number(x), do: :nan
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
Expand Down Expand Up @@ -779,7 +788,12 @@ defmodule Complex do
def sqrt(:infinity), do: :infinity
def sqrt(:neg_infinity), do: :nan
def sqrt(:nan), do: :nan
def sqrt(n) when is_number(n), do: :math.sqrt(n)

def sqrt(n) when is_number(n) do
:math.sqrt(n)
rescue
ArithmeticError -> :nan
end

def sqrt(%Complex{re: :nan}), do: Complex.new(:nan, :nan)
def sqrt(%Complex{im: :nan}), do: Complex.new(:nan, :nan)
Expand Down Expand Up @@ -882,7 +896,12 @@ defmodule Complex do
def exp(:infinity), do: :infinity
def exp(:neg_infinity), do: 0
def exp(:nan), do: :nan
def exp(n) when is_number(n), do: :math.exp(n)

def exp(n) when is_number(n) do
:math.exp(n)
rescue
ArithmeticError -> :infinity
end

def exp(%Complex{re: :neg_infinity, im: _}), do: new(0, 0)
def exp(%Complex{re: :infinity, im: :nan}), do: new(:infinity, :nan)
Expand Down Expand Up @@ -1126,7 +1145,16 @@ defmodule Complex do
def pow(_, :infinity), do: :infinity

def pow(x, y) when is_integer(x) and is_integer(y) and y >= 0, do: Integer.pow(x, y)
def pow(x, y) when is_number(x) and is_number(y), do: :math.pow(x, y)

def pow(x, y) when is_number(x) and is_number(y) do
:math.pow(x, y)
rescue
ArithmeticError ->
cond do
x == 0 and y < 0 -> :infinity
true -> :nan
end
end

def pow(x, y) do
x = as_complex(x)
Expand Down Expand Up @@ -1243,7 +1271,11 @@ defmodule Complex do
@spec asin(t | number | non_finite_number) :: t | number | non_finite_number
def asin(z)

def asin(n) when is_number(n), do: :math.asin(n)
def asin(n) when is_number(n) do
:math.asin(n)
rescue
ArithmeticError -> :nan
end

def asin(n) when is_non_finite_number(n), do: :nan

Expand Down Expand Up @@ -1317,7 +1349,11 @@ defmodule Complex do
@spec acos(t | number | non_finite_number) :: t | number | non_finite_number
def acos(z)

def acos(n) when is_number(n), do: :math.acos(n)
def acos(n) when is_number(n) do
:math.acos(n)
rescue
ArithmeticError -> :nan
end

def acos(n) when is_non_finite_number(n), do: :nan

Expand Down Expand Up @@ -1452,7 +1488,11 @@ defmodule Complex do
@spec cot(t | number | non_finite_number) :: t | number | non_finite_number
def cot(z)

def cot(n) when is_number(n), do: 1 / :math.tan(n)
def cot(n) when is_number(n) do
1 / :math.tan(n)
rescue
ArithmeticError -> :infinity
end

def cot(z) do
divide(cos(z), sin(z))
Expand All @@ -1478,7 +1518,11 @@ defmodule Complex do
@spec acot(t | number | non_finite_number) :: t | number | non_finite_number
def acot(z)

def acot(n) when is_number(n), do: :math.atan(1 / n)
def acot(n) when is_number(n) do
:math.atan(1 / n)
rescue
ArithmeticError -> :math.pi() / 2
end

def acot(:infinity), do: 0
def acot(:neg_infinity), do: :math.pi()
Expand Down Expand Up @@ -1592,7 +1636,13 @@ defmodule Complex do
@spec acsc(t | number | non_finite_number) :: t | number | non_finite_number
def acsc(z)

def acsc(n) when is_number(n), do: :math.asin(1 / n)
def acsc(n) when is_number(n) do
:math.asin(1 / n)
rescue
ArithmeticError ->
if n == 0, do: :infinity, else: :nan
end

def acsc(:infinity), do: 0
def acsc(:neg_infinity), do: -:math.pi()
def acsc(:nan), do: :nan
Expand Down Expand Up @@ -1630,7 +1680,11 @@ defmodule Complex do

def sinh(n) when is_non_finite_number(n), do: n

def sinh(n) when is_number(n), do: :math.sinh(n)
def sinh(n) when is_number(n) do
:math.sinh(n)
rescue
ArithmeticError -> if n > 0, do: :infinity, else: :neg_infinity
end

def sinh(z = %Complex{}) do
%Complex{re: re, im: im} =
Expand Down Expand Up @@ -1703,7 +1757,12 @@ defmodule Complex do
def cosh(:infinity), do: :infinity
def cosh(:neg_infinity), do: :infinity
def cosh(:nan), do: :nan
def cosh(n) when is_number(n), do: :math.cosh(n)

def cosh(n) when is_number(n) do
:math.cosh(n)
rescue
ArithmeticError -> :infinity
end

def cosh(z) do
%Complex{re: re, im: im} =
Expand Down Expand Up @@ -1732,10 +1791,16 @@ defmodule Complex do
def acosh(z)

if math_fun_supported?.(:acosh, 1) do
def acosh(n) when is_number(n), do: :math.acosh(n)
def acosh(n) when is_number(n) do
:math.acosh(n)
rescue
ArithmeticError -> :nan
end
else
def acosh(n) when is_number(n) do
:math.log(n + :math.sqrt(n * n - 1))
rescue
ArithmeticError -> :nan
end
end

Expand Down Expand Up @@ -1798,11 +1863,22 @@ defmodule Complex do
@spec atanh(t | number | non_finite_number) :: t | number | non_finite_number
def atanh(z)

def atanh(1), do: :infinity
def atanh(1.0), do: :infinity
def atanh(-1), do: :neg_infinity
def atanh(-1.0), do: :neg_infinity

if math_fun_supported?.(:atanh, 1) do
def atanh(n) when is_number(n), do: :math.atanh(n)
def atanh(n) when is_number(n) do
:math.atanh(n)
rescue
ArithmeticError -> :nan
end
else
def atanh(n) when is_number(n) do
0.5 * :math.log((1 + n) / (1 - n))
rescue
ArithmeticError -> :nan
end
end

Expand Down
184 changes: 184 additions & 0 deletions test/complex_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -1017,4 +1017,188 @@ defmodule ComplexTest do
""")
end
end

# ── IEEE 754 compliance ──────────────────────────────────────────

describe "IEEE 754: overflow returns Inf instead of crashing" do
test "exp(large) returns :infinity" do
assert Complex.exp(1000) == :infinity
assert Complex.exp(1000.0) == :infinity
end

test "sinh(large positive) returns :infinity" do
assert Complex.sinh(1000) == :infinity
assert Complex.sinh(1000.0) == :infinity
end

test "sinh(large negative) returns :neg_infinity" do
assert Complex.sinh(-1000) == :neg_infinity
assert Complex.sinh(-1000.0) == :neg_infinity
end

test "cosh(large) returns :infinity" do
assert Complex.cosh(1000) == :infinity
assert Complex.cosh(1000.0) == :infinity
end
end

describe "IEEE 754: domain errors return NaN instead of crashing" do
test "asin outside [-1, 1]" do
assert Complex.asin(2.0) == :nan
assert Complex.asin(-2.0) == :nan
end

test "acos outside [-1, 1]" do
assert Complex.acos(2.0) == :nan
assert Complex.acos(-2.0) == :nan
end

test "acosh below 1" do
assert Complex.acosh(0.5) == :nan
assert Complex.acosh(-1.0) == :nan
end

test "atanh outside (-1, 1)" do
assert Complex.atanh(2.0) == :nan
assert Complex.atanh(-2.0) == :nan
end

test "atanh at boundaries" do
assert Complex.atanh(1.0) == :infinity
assert Complex.atanh(1) == :infinity
assert Complex.atanh(-1.0) == :neg_infinity
assert Complex.atanh(-1) == :neg_infinity
end
end

describe "IEEE 754: division by zero" do
test "positive / 0.0 = :infinity" do
assert Complex.divide(1.0, 0.0) == :infinity
assert Complex.divide(5, 0.0) == :infinity
end

test "negative / 0.0 = :neg_infinity" do
assert Complex.divide(-1.0, 0.0) == :neg_infinity
assert Complex.divide(-5, 0.0) == :neg_infinity
end

test "0.0 / 0.0 = :nan" do
assert Complex.divide(0.0, 0.0) == :nan
assert Complex.divide(0, 0.0) == :nan
end

test "positive / -0.0 = :neg_infinity" do
assert Complex.divide(1.0, -0.0) == :neg_infinity
end

test "negative / -0.0 = :infinity" do
assert Complex.divide(-1.0, -0.0) == :infinity
end

test "normal division still works" do
assert Complex.divide(6.0, 3.0) == 2.0
assert Complex.divide(10, 2) == 5
end
end

describe "IEEE 754: sqrt domain errors" do
test "sqrt(negative) returns :nan" do
assert Complex.sqrt(-1) == :nan
assert Complex.sqrt(-1.0) == :nan
assert Complex.sqrt(-4.0) == :nan
end
end

describe "IEEE 754: pow edge cases" do
test "pow(0, negative) returns :infinity" do
assert Complex.pow(0, -1) == :infinity
assert Complex.pow(0.0, -1.0) == :infinity
end
end

describe "IEEE 754: cot/acot/acsc domain errors" do
test "cot(0) returns :infinity (1/tan(0) = 1/0)" do
assert Complex.cot(0) == :infinity
assert Complex.cot(0.0) == :infinity
end

test "acot(0) returns pi/2" do
assert_close Complex.acot(0), :math.pi() / 2
assert_close Complex.acot(0.0), :math.pi() / 2
end

test "acsc(0) returns :infinity" do
assert Complex.acsc(0) == :infinity
assert Complex.acsc(0.0) == :infinity
end

test "acsc(0.5) returns :nan (asin(2) domain error)" do
assert Complex.acsc(0.5) == :nan
end
end

describe "IEEE 754: integer division by zero" do
test "divide(1, 0) returns :infinity" do
assert Complex.divide(1, 0) == :infinity
end

test "divide(-1, 0) returns :neg_infinity" do
assert Complex.divide(-1, 0) == :neg_infinity
end

test "divide(0, 0) returns :nan" do
assert Complex.divide(0, 0) == :nan
end
end

describe "IEEE 754: pow domain errors" do
test "pow(-1, 0.5) returns :nan (sqrt of negative)" do
assert Complex.pow(-1, 0.5) == :nan
assert Complex.pow(-1.0, 0.5) == :nan
end
end

describe "IEEE 754: log edge cases" do
test "log(0) returns :neg_infinity" do
assert Complex.log(0) == :neg_infinity
assert Complex.log(0.0) == :neg_infinity
end

test "log(negative) returns :nan" do
assert Complex.log(-1.0) == :nan
end

test "log10(0) returns :neg_infinity" do
assert Complex.log10(0) == :neg_infinity
end

test "log2(0) returns :neg_infinity" do
assert Complex.log2(0) == :neg_infinity
end
end

describe "IEEE 754: tanh at extremes" do
test "tanh(large) clamps to 1/-1" do
assert Complex.tanh(1000) == 1.0
assert Complex.tanh(-1000) == -1.0
end
end

describe "IEEE 754: normal values still work" do
test "exp(0) == 1" do
assert Complex.exp(0) == 1.0
end

test "asin(0.5) is correct" do
assert_close Complex.asin(0.5), :math.asin(0.5)
end

test "sinh(1) is correct" do
assert_close Complex.sinh(1.0), :math.sinh(1.0)
end

test "cosh(1) is correct" do
assert_close Complex.cosh(1.0), :math.cosh(1.0)
end
end
end