-module(rational). -compile([no_auto_import, nowarn_unused_vars]). -export([new/2, new_improper/3, from_float/2, from_int/1, to_float/1, truncate/1, to_mixed_fraction/1, add/2, subtract/2, multiply/2, divide/2, reciprocal/1, pow/2, absolute_value/1, compare/2]). -export_type([rational/0]). -opaque rational() :: {rational, integer(), integer()}. -spec gcd(integer(), integer()) -> integer(). gcd(X, Y) -> case {X, Y} of {0, _} -> Y; {_, 0} -> X; {_, _} -> _assert_subject = gleam@int:modulo(Y, X), {ok, Z} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"rational"/utf8>>, function => <<"gcd"/utf8>>, line => 15}) end, gcd(Z, X) end. -spec reduce(integer(), integer()) -> rational(). reduce(Num, Den) -> G = gcd(gleam@int:absolute_value(Num), gleam@int:absolute_value(Den)), case Den < 0 of true -> {rational, case G of 0 -> 0; Gleam@denominator -> - Num div Gleam@denominator end, case G of 0 -> 0; Gleam@denominator@1 -> Den div Gleam@denominator@1 end}; false -> {rational, case G of 0 -> 0; Gleam@denominator@2 -> Num div Gleam@denominator@2 end, case G of 0 -> 0; Gleam@denominator@3 -> Den div Gleam@denominator@3 end} end. -spec do_pow(integer(), integer()) -> integer(). do_pow(X, N) -> case N of 0 -> 1; 1 -> X; 2 -> X * X; N@1 -> case N@1 rem 2 of 0 -> do_pow(X * X, N@1 div 2); 1 -> X * do_pow(X, N@1 - 1) end end. -spec new(integer(), integer()) -> {ok, rational()} | {error, nil}. new(Num, Den) -> case Den of 0 -> {error, nil}; _ -> {ok, reduce(Num, Den)} end. -spec new_improper(integer(), integer(), integer()) -> {ok, rational()} | {error, nil}. new_improper(Whole, Num, Den) -> new(Num + (Whole * Den), Den). -spec from_float(float(), rational()) -> rational(). from_float(F, Inc) -> reduce(gleam@float:round(case gleam@int:to_float(erlang:element(2, Inc)) of 0.0 -> 0.0; Gleam@denominator -> (F * gleam@int:to_float( erlang:element(3, Inc) )) / Gleam@denominator end), erlang:element(3, Inc)). -spec from_int(integer()) -> rational(). from_int(From) -> _assert_subject = new(From, 1), {ok, R} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"rational"/utf8>>, function => <<"from_int"/utf8>>, line => 72}) end, R. -spec to_float(rational()) -> float(). to_float(R) -> case gleam@int:to_float(erlang:element(3, R)) of 0.0 -> 0.0; Gleam@denominator -> gleam@int:to_float(erlang:element(2, R)) / Gleam@denominator end. -spec truncate(rational()) -> integer(). truncate(R) -> case erlang:element(3, R) of 0 -> 0; Gleam@denominator -> erlang:element(2, R) div Gleam@denominator end. -spec to_mixed_fraction(rational()) -> {integer(), rational()}. to_mixed_fraction(R) -> {case erlang:element(3, R) of 0 -> 0; Gleam@denominator -> erlang:element(2, R) div Gleam@denominator end, reduce(case erlang:element(3, R) of 0 -> 0; Gleam@denominator@1 -> erlang:element(2, R) rem Gleam@denominator@1 end, erlang:element(3, R))}. -spec add(rational(), rational()) -> rational(). add(A, B) -> reduce( (erlang:element(2, A) * erlang:element(3, B)) + (erlang:element(2, B) * erlang:element( 3, A )), erlang:element(3, A) * erlang:element(3, B) ). -spec subtract(rational(), rational()) -> rational(). subtract(A, B) -> reduce( (erlang:element(2, A) * erlang:element(3, B)) - (erlang:element(2, B) * erlang:element( 3, A )), erlang:element(3, A) * erlang:element(3, B) ). -spec multiply(rational(), rational()) -> rational(). multiply(A, B) -> reduce( erlang:element(2, A) * erlang:element(2, B), erlang:element(3, A) * erlang:element(3, B) ). -spec divide(rational(), rational()) -> {ok, rational()} | {error, nil}. divide(A, B) -> case erlang:element(2, B) of 0 -> {error, nil}; _ -> {ok, reduce( erlang:element(2, A) * erlang:element(3, B), erlang:element(3, A) * erlang:element(2, B) )} end. -spec reciprocal(rational()) -> rational(). reciprocal(A) -> reduce(erlang:element(3, A), erlang:element(2, A)). -spec pow(rational(), integer()) -> rational(). pow(A, N) -> case gleam@int:compare(N, 0) of gt -> reduce( do_pow(erlang:element(2, A), N), do_pow(erlang:element(3, A), N) ); eq -> from_int(1); lt -> reduce( do_pow(erlang:element(3, A), - N), do_pow(erlang:element(2, A), - N) ) end. -spec absolute_value(rational()) -> rational(). absolute_value(A) -> reduce(gleam@int:absolute_value(erlang:element(2, A)), erlang:element(3, A)). -spec compare(rational(), rational()) -> gleam@order:order(). compare(A, B) -> gleam@int:compare( erlang:element(2, A) * erlang:element(3, B), erlang:element(2, B) * erlang:element(3, A) ).