exercism/elixir/rational-numbers/lib/rational_numbers.ex

81 lines
2.5 KiB
Elixir

defmodule RationalNumbers do
@type rational :: {integer, integer}
alias Kernel, as: K
@doc """
Add two rational numbers
"""
@spec add(a :: rational, b :: rational) :: rational
def add({an, ad}, {bn, bd}), do: {an * bd + bn * ad, ad * bd} |> reduce()
@doc """
Subtract two rational numbers
"""
@spec subtract(a :: rational, b :: rational) :: rational
def subtract({an, ad}, {bn, bd}), do: {an * bd - bn * ad, ad * bd} |> reduce()
@doc """
Multiply two rational numbers
"""
@spec multiply(a :: rational, b :: rational) :: rational
def multiply({an, ad}, {bn, bd}), do: {an * bn, ad * bd} |> reduce()
@doc """
Divide two rational numbers
"""
@spec divide_by(num :: rational, den :: rational) :: rational
def divide_by({an, ad}, {bn, bd}) when bn != 0, do: {an * bd, bn * ad} |> reduce()
@doc """
Absolute value of a rational number
"""
@spec abs(a :: rational) :: rational
def abs({an, ad}), do: {K.abs(an), K.abs(ad)} |> reduce()
@doc """
Exponentiation of a rational number by an integer
"""
@spec pow_rational(a :: rational, n :: integer | float) :: rational
def pow_rational({an, ad}, n) when is_integer(n) and n >= 0, do: {an ** n, ad ** n} |> reduce()
def pow_rational({an, ad}, n) when is_integer(n) and n < 0,
do: {ad ** K.abs(n), an ** K.abs(n)} |> reduce()
def pow_rational({an, ad}, x) when is_float(x), do: ad ** x / an ** x
@doc """
Exponentiation of a real number by a rational number
"""
@spec pow_real(x :: integer, n :: rational) :: float
def pow_real(x, {_nn, nd} = n) when is_integer(x) and nd < 0, do: pow_real(x, normalize(n))
def pow_real(x, {nn, nd}) when is_integer(x), do: x ** nn ** (1 / nd)
@doc """
Reduce a rational number to its lowest terms
"""
@spec reduce(a :: rational) :: rational
def reduce({an, ad} = a),
do: a |> normalize() |> cut(Integer.gcd(an, ad))
# @doc """
# Divides a nominator and denominator by integer
# """
@spec cut(a :: rational, n :: integer) :: rational
defp cut({an, ad}, n), do: {K.div(an, n), K.div(ad, n)}
# @doc """
# Turns signs of nominator and denominator when negative denominator
# """
@spec normalize(a :: rational) :: rational
defp normalize({an, ad}) when ad < 0, do: {K.-(an), K.-(ad)}
defp normalize({an, ad}), do: {an, ad}
# @doc """
# Calculate a greatest common divisor of two integers
# """
# @spec gcd(i :: integer, j :: integer) :: integer
# defp gcd(i, 0), do: K.abs(i)
# defp gcd(0, j), do: K.abs(j)
# defp gcd(i, j), do: gcd(j, K.rem(i, j))
end