Implement abs for each angle.

This commit is contained in:
James Harton 2017-11-05 13:45:07 +13:00
parent 7b021e8a83
commit d606863684
8 changed files with 156 additions and 17 deletions

1
.gitignore vendored
View file

@ -9,6 +9,7 @@
# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/
/docs/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,7 @@
defmodule Angle do
defstruct ~w(d r g dms)a
alias Angle.{Radian, Gradian, Degree, DMS, Trig}
@moduledoc """
Tired of forever converting back and forwards between degrees and radians?
Well worry no more; Angle is here to make your life simple!
@ -61,10 +63,10 @@ defmodule Angle do
end
end
defdelegate degrees(n), to: Angle.Degree, as: :init
defdelegate radians(n), to: Angle.Radian, as: :init
defdelegate gradians(n), to: Angle.Gradian, as: :init
defdelegate dms(d, m, s), to: Angle.DMS, as: :init
defdelegate degrees(n), to: Degree, as: :init
defdelegate radians(n), to: Radian, as: :init
defdelegate gradians(n), to: Gradian, as: :init
defdelegate dms(d, m, s), to: DMS, as: :init
@doc """
Initialize and Angle with zero values
@ -77,8 +79,49 @@ defmodule Angle do
@spec zero() :: t
def zero, do: %Angle{d: 0, r: 0, g: 0}
defdelegate to_radians(angle), to: Angle.Radian
defdelegate to_degrees(angle), to: Angle.Degree
defdelegate to_gradians(angle), to: Angle.Gradian
defdelegate to_dms(angle), to: Angle.DMS
defdelegate to_radians(angle), to: Radian
defdelegate to_degrees(angle), to: Degree
defdelegate to_gradians(angle), to: Gradian
defdelegate to_dms(angle), to: DMS
defdelegate acos(x), to: Trig
defdelegate acosh(x), to: Trig
defdelegate asin(x), to: Trig
defdelegate asinh(x), to: Trig
defdelegate atan(x), to: Trig
defdelegate atan2(x, y), to: Trig
defdelegate cos(angle), to: Trig
defdelegate cosh(angle), to: Trig
defdelegate sin(angle), to: Trig
defdelegate sinh(angle), to: Trig
defdelegate tan(angle), to: Trig
defdelegate tanh(angle), to: Trig
@doc """
Convert the angle to it's absolute value by discarding complete revolutions
and converting negatives.
## Examples
iex> ~a(-270)d
...> |> Angle.abs()
#Angle<90°>
iex> ~a(-4.71238898038469)r
...> |> Angle.abs()
#Angle<1.5707963267948966㎭>
iex> ~a(-270,15,45)dms
...> |> Angle.abs()
#Angle<90° 45 15″>
iex> ~a(-300)g
...> |> Angle.abs()
#Angle<100ᵍ>
"""
@spec abs(Angle.t) :: Angle.t
def abs(%Angle{r: r} = angle) when is_number(r), do: Radian.abs(angle)
def abs(%Angle{d: d} = angle) when is_number(d), do: Degree.abs(angle)
def abs(%Angle{g: g} = angle) when is_number(g), do: Gradian.abs(angle)
def abs(%Angle{dms: {d, m, s}} = angle) when is_number(d) and is_number(m) and is_number(s), do: DMS.abs(angle)
end

View file

@ -35,10 +35,13 @@ defmodule Angle.Degree do
iex> "13.2°" |> parse() |> inspect()
"{:ok, #Angle<13.2°>}"
iex> "-13.2°" |> parse() |> inspect()
"{:ok, #Angle<-13.2°>}"
"""
@spec parse(String.t) :: {:ok, Angle.t} | {:error, term}
def parse(value) do
case Regex.run(~r/^[0-9]+(?:\.[0-9]+)?/, value) do
case Regex.run(~r/^-?[0-9]+(?:\.[0-9]+)?/, value) do
[value] ->
case string_to_number(value) do
{:ok, n} -> {:ok, init(n)}
@ -100,4 +103,25 @@ defmodule Angle.Degree do
@spec to_degrees(Angle.t) :: {Angle.t, number}
def to_degrees(%Angle{d: number} = angle) when is_number(number), do: {angle, number}
def to_degrees(angle), do: angle |> ensure() |> to_degrees()
@doc """
Convert the angle to it's absolute value by discarding complete revolutions
and converting negatives.
## Examples
iex> ~a(-270)d
...> |> Angle.Degree.abs()
#Angle<90°>
iex> ~a(1170)d
...> |> Angle.Degree.abs()
#Angle<90°>
"""
@spec abs(Angle.t) :: Angle.t
def abs(%Angle{d: d}), do: init(calculate_abs(d))
defp calculate_abs(d) when d >= 0 and d <= 360, do: d
defp calculate_abs(d) when d > 360, do: calculate_abs(d - 360)
defp calculate_abs(d) when d < 0, do: calculate_abs(d + 360)
end

View file

@ -5,7 +5,7 @@ defmodule Angle.DMS do
Functions relating to dealing with angles in Degrees, Minutes and Seconds.
"""
@parser ~r/([0-9]+)
@parser ~r/(-?[0-9]+)
(?:[\x{00b0},\ ]?\ *)
([0-9]+)
(?:[\x{2032}',\ ]?\ *)
@ -63,6 +63,9 @@ defmodule Angle.DMS do
iex> "166°4558.46″" |> parse() |> inspect()
"{:ok, #Angle<166° 45 58.46″>}"
iex> "-166° 45 58.46″" |> parse() |> inspect()
"{:ok, #Angle<-166° 45 58.46″>}"
"""
@spec parse(String.t) :: {:ok, Angle.t} | {:error, term}
def parse(value) do
@ -136,4 +139,26 @@ defmodule Angle.DMS do
@spec to_dms(Angle.t) :: {Angle.t, {integer, integer, number}}
def to_dms(%Angle{dms: {d, m, s}} = angle), do: {angle, {d, m, s}}
def to_dms(%Angle{} = angle), do: angle |> ensure() |> to_dms()
@doc """
Convert the angle to it's absolute value by discarding complete revolutions
and converting negatives.
## Examples
iex> ~a(-270,15,45)dms
...> |> Angle.DMS.abs()
#Angle<90° 45 15″>
iex> ~a(1170,0,0)dms
...> |> Angle.DMS.abs()
#Angle<90°>
"""
@spec abs(Angle.t) :: Angle.t
def abs(%Angle{dms: {d, m, s}}), do: %Angle{dms: calculate_abs(d, m, s)}
defp calculate_abs(d, m, s) when d >= 0 and d <= 360, do: {d, m, s}
defp calculate_abs(d, m, s) when d > 360, do: calculate_abs(d - 360, m, s)
defp calculate_abs(d, m, s) when d < -360, do: calculate_abs(d + 360, m, s)
defp calculate_abs(d, m, s) when d < 0, do: calculate_abs(d + 360, 60 - m, 60 - s)
end

View file

@ -39,7 +39,7 @@ defmodule Angle.Gradian do
"""
@spec parse(String.t) :: {:ok, Angle.t} | {:error, term}
def parse(value) do
case Regex.run(~r/^[0-9]+(?:\.[0-9]+)?/, value) do
case Regex.run(~r/^-?[0-9]+(?:\.[0-9]+)?/, value) do
[value] ->
case string_to_number(value) do
{:ok, n} -> {:ok, init(n)}
@ -109,4 +109,24 @@ defmodule Angle.Gradian do
|> to_gradians()
end
@doc """
Convert the angle to it's absolute value by discarding complete revolutions
and converting negatives.
## Examples
iex> ~a(-300)g
...> |> Angle.Gradian.abs()
#Angle<100ᵍ>
iex> ~a(1300)g
...> |> Angle.Gradian.abs()
#Angle<100ᵍ>
"""
@spec abs(Angle.t) :: Angle.t
def abs(%Angle{g: g}), do: init(calculate_abs(g))
defp calculate_abs(g) when g >= 0 and g <= 400, do: g
defp calculate_abs(g) when g > 400, do: calculate_abs(g - 400)
defp calculate_abs(g) when g < 0, do: calculate_abs(g + 400)
end

View file

@ -1,9 +1,12 @@
defmodule Angle.Radian do
import Angle.Utils, only: [string_to_number: 1]
@moduledoc """
Functions relating to dealing with angles in Radians.
"""
@two_pi 2 * :math.pi()
@doc """
Initialize an Angle from a number `n` of radians
@ -20,7 +23,7 @@ defmodule Angle.Radian do
def init(n) when is_number(n), do: %Angle{r: n}
@doc """
Attempt to arse decimal radians.
Attempt to parse decimal radians.
## Examples
@ -35,10 +38,13 @@ defmodule Angle.Radian do
iex> "13.2㎭" |> parse() |> inspect()
"{:ok, #Angle<13.2㎭>}"
iex> "-13.2㎭" |> parse() |> inspect()
"{:ok, #Angle<-13.2㎭>}"
"""
@spec parse(String.t) :: {:ok, Angle.t} | {:error, term}
def parse(value) do
case Regex.run(~r/^[0-9]+(?:\.[0-9]+)?/, value) do
case Regex.run(~r/^-?[0-9]+(?:\.[0-9]+)?/, value) do
[value] ->
case string_to_number(value) do
{:ok, n} -> {:ok, init(n)}
@ -94,12 +100,12 @@ defmodule Angle.Radian do
## Examples
iex> use Angle
...> ~a(90)d
...> |> Angle.Radian.to_radians()
iex> ~a(90)d
...> |> to_radians()
...> |> inspect()
"{#Angle<90°>, 1.5707963267948966}"
"""
@spec to_radians(Angle.t) :: {Angle.t, number}
def to_radians(%Angle{r: number} = angle) when is_number(number), do: {angle, number}
def to_radians(angle) do
angle
@ -107,4 +113,24 @@ defmodule Angle.Radian do
|> to_radians()
end
@doc """
Convert the angle to it's absolute value by discarding complete revolutions
and converting negatives.
## Examples
iex> ~a(-4.71238898038469)r
...> |> Angle.Radian.abs()
#Angle<1.5707963267948966㎭>
iex> ~a(20.420352248333657)r
...> |> Angle.Radian.abs()
#Angle<1.5707963267948983㎭>
"""
@spec abs(Angle.t) :: Angle.t
def abs(%Angle{r: r}), do: init(calculate_abs(r))
defp calculate_abs(r) when r >= 0 and r <= @two_pi, do: r
defp calculate_abs(r) when r > @two_pi, do: calculate_abs(r - @two_pi)
defp calculate_abs(r) when r < 0, do: calculate_abs(r + @two_pi)
end

View file

@ -1,4 +1,5 @@
defmodule AngleTest do
use ExUnit.Case
import Angle.Sigil
doctest Angle
end