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