Packages

A basic rational number library for Gleam

Retired package: Deprecated - Incomplete

Current section

Files

Jump to
rizzo src rational.erl
Raw

src/rational.erl

-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)
).