Packages

A library to handle primary school mathematics operations

Current section

Files

Jump to
toothmath lib fraction.ex
Raw

lib/fraction.ex

defmodule Toothmath.Fraction do
@moduledoc """
This is a hand spun math module to handle Fractions
"""
alias Toothmath.Fraction
@doc """
A fraction
* `:a`: the top (numerator) value
* `:b` the bottom (denominator) value
"""
defstruct a: nil, b: nil
@doc """
Create a new fraction with numerator and denominator such as 1 / 2
* `a` - the numerator
* `b` - the denominator
"""
def new(a, b) do
%Fraction{a: a, b: b}
end
@doc """
Get the rational number
a fractional number is always a rational number, but a rational number may or may not be a fractional number
## Examples
```
iex> alias Toothmath.Fraction
iex> Fraction.value(%Fraction{a: 1, b: 2})
0.50
```
"""
def value(%Fraction{a: a, b: b}) do
a / b
end
@doc """
Add 2 fractions together in simplified form
## Examples
```
iex> alias Toothmath.Fraction
iex> Fraction.add(%Fraction{a: 1, b: 2}, %Fraction{a: 1, b: 4})
%Fraction{a: 3, b: 4}
```
"""
def add(%Fraction{a: a1, b: b1}, %Fraction{a: a2, b: b2}) do
new(
a1 * b2 + a2 * b1,
b2 * b1
)
|> simplify()
end
@doc """
Simply a fraction using the greatest common divisor using the Euclidean algorithm
```
iex> alias Toothmath.Fraction
iex> Fraction.simplify(%Fraction{a: 2, b: 4})
%Fraction{a: 1, b: 2}
```
"""
def simplify(%Fraction{a: a, b: b}) do
gcd = gcd(a, b)
new(div(a, gcd), div(b, gcd))
end
@doc ~S"""
Approximates the given `x` float value into a fraction
## Examples
iex> alias Toothmath.Fraction
iex> Fraction.from_value(0.6875)
%Fraction{a: 11, b: 16}
"""
def from_value(x, max_denominator \\ 100) do
# Try to find the most accurate fraction
find_best_fraction = fn ->
Enum.find_value(1..max_denominator, fn denominator ->
# Try different numerators
numerator = round(x * denominator)
# Check if this fraction is close to the original value
fraction_value = numerator / denominator
if abs(fraction_value - x) < 1.0e-10 do
{numerator, denominator}
else
nil
end
end)
end
case find_best_fraction.() do
{a, b} ->
new(a, b) |> simplify()
nil ->
# Fallback to the previous approximation method
do_from_value(x, max_denominator)
end
end
# Fallback approximation method (previous implementation)
defp do_from_value(x, max_denominator) do
mediant = fn {a1, b1}, {a2, b2} -> {a1 + a2, b1 + b2} end
{lower_a, lower_b} = {0, 1}
{upper_a, upper_b} = {1, 0}
do_approximate = fn
_, _, {a, b}, _ when abs(a / b - x) < 1.0e-10 ->
new(a, b)
_, max_denom, {lower_a, lower_b}, {upper_a, upper_b} when lower_b + upper_b > max_denom ->
{a, b} =
if abs(lower_a / lower_b - x) < abs(upper_a / upper_b - x),
do: {lower_a, lower_b},
else: {upper_a, upper_b}
new(a, b)
f, _max_denom, lower, upper ->
{mediant_a, mediant_b} = f.(lower, upper)
cond do
mediant_a / mediant_b > x ->
f.(lower, {mediant_a, mediant_b})
true ->
f.({mediant_a, mediant_b}, upper)
end
end
do_approximate.(mediant, max_denominator, {lower_a, lower_b}, {upper_a, upper_b})
end
# https://en.wikipedia.org/wiki/Greatest_common_divisor
defp gcd(a, 0), do: abs(a)
defp gcd(a, b), do: gcd(b, rem(a, b))
end