import glat/util import gleam/float import gleam/int import gleam/list import gleam/order import gleam/result import gleam/string pub type Rational { /// ⚠⚠ It is unsafe to raw construct this from unknown input, /// as these functions assume the rational they take in do not have a 0 or negative denominator. /// You should use `new`. Rational(num: Int, den: Int) } /// 0/1 pub const zero = Rational(0, 1) /// 1/1 pub const one = Rational(1, 1) /// Returns Error if the denominator is 0. pub fn new_check(num: Int, over den: Int) -> Result(Rational, Nil) { case den { 0 -> Error(Nil) _ -> Ok(Rational(num:, den:) |> shift_negative) } } /// Returns [`zero`](#zero) if the denominator is 0. pub fn new(num: Int, over den: Int) -> Rational { new_check(num, den) |> result.unwrap(zero) } /// `num` / 1. pub fn from_int(num: Int) -> Rational { Rational(num:, den: 1) } /// This returns (0..=den)/den. \ /// A negative denominator will negate the result. /// /// Returns Error if the integer is 0. pub fn random_check(over den: Int) -> Result(Rational, Nil) { new_check(int.random(int.absolute_value(den) + 1), den) } /// This returns (0..=den)/den. \ /// A negative denominator will negate the result. /// /// Returns [`zero`](#zero) if den is 0. pub fn random(over den: Int) -> Rational { unwrap(random_check(den)) } /// Returns Error if the float is 0.0. pub fn from_float_check(f: Float) -> Result(Rational, Nil) { case f { 0.0 -> Error(Nil) _ -> { let assert [whole_str, fract_str] = float.to_string(f) |> string.split(".") let fract_str = case fract_str { "0" -> "" x -> x } let power = string.length(fract_str) let assert Ok(num) = int.parse(whole_str <> fract_str) let den = util.int_power(10, power) Ok(Rational(num, den)) } } } /// Returns [`zero`](#zero) if the float is 0.0. pub fn from_float(f: Float) -> Rational { from_float_check(f) |> result.unwrap(zero) } /// Only parses `{num}/{den}`. /// /// Returns Error if the parsing fails, or the denominator is 0. pub fn parse_check(str: String) -> Result(Rational, Nil) { use #(num, den) <- result.try(do_parse(str)) new_check(num, den) } /// Only parses `{num}/{den}`. /// /// Returns Error if the parsing fails. \ /// Returns [`zero`](#zero) if the denominator is 0. pub fn parse(str: String) -> Result(Rational, Nil) { use #(num, den) <- result.map(do_parse(str)) new(num, den) } fn do_parse(str: String) -> Result(#(Int, Int), Nil) { case str |> string.split("/") { [num, den] -> { use num <- result.try(int.parse(num)) use den <- result.try(int.parse(den)) Ok(#(num, den)) } _ -> Error(Nil) } } /// Result of num / den. /// Also getting the whole part of a mixed fraction. pub fn to_int(rat: Rational) -> Int { rat.num / rat.den } /// Result of int.floor_divide(num, den). /// Also getting the whole part of a mixed fraction. pub fn to_int_floor(rat: Rational) -> Int { rat.num |> int.floor_divide(rat.den) |> result.unwrap(0) } /// Panics if the numerator or denominator are not in range of a Float. pub fn to_float(rat: Rational) -> Float { int.to_float(rat.num) /. int.to_float(rat.den) } /// Join the numerator and denominator with a "/". pub fn to_string(rat: Rational) -> String { int.to_string(rat.num) <> "/" <> int.to_string(rat.den) } pub fn is_negative(rat: Rational) -> Bool { rat.num < 0 } pub fn is_positive(rat: Rational) -> Bool { rat.num >= 0 } /// Simplify the rational. pub fn reduce(rat: Rational) -> Rational { let gcd = util.gcd(rat.num, rat.den) new(rat.num / gcd, rat.den / gcd) } /// |num| / den pub fn absolute_value(rat: Rational) -> Rational { Rational(..rat, num: int.absolute_value(rat.num)) } /// Swap the numerator and denominator. /// /// Returns an Error if the numerator is 0. pub fn flip_check(rat: Rational) -> Result(Rational, Nil) { new_check(rat.den, rat.num) } /// Swap the numerator and denominator. /// /// Returns [`zero`](#zero) if the numerator is 0. pub fn flip(rat: Rational) -> Rational { flip_check(rat) |> result.unwrap(zero) } /// TODO: make this function (or a new function) not have a float limitation. /// /// Panics if the numerator and denominator are not in the bounds of a float. \ /// Returns Error if the rational is negative. pub fn square_root_check(rat: Rational) -> Result(Rational, Nil) { case rat.num { 0 -> Ok(rat) _ -> { use num <- result.try(int.square_root(rat.num)) use den <- result.try(int.square_root(rat.den)) Ok(divide(from_float(num), from_float(den))) } } } /// TODO: make this function (or a new function) not have a float limitation. /// /// Panics if the numerator and denominator are not in the bounds of a float. \ /// Returns [`zero`](#zero) if the rational is negative. pub fn square_root(rat: Rational) -> Rational { square_root_check(rat) |> result.unwrap(zero) } /// This uses truncated division like the `%` operator does. /// See [`modulo`](#modulo) for floor division. pub fn remainder(rat: Rational) -> Int { let assert Ok(num) = int.remainder(rat.num, rat.den) num } /// This uses floor division. /// See [`remainder`](#remainder) for truncated division. pub fn modulo(rat: Rational) -> Int { let assert Ok(num) = int.modulo(rat.num, rat.den) num } /// Get the fractional part of a mixed fraction. /// Uses truncated division. pub fn fraction(rat: Rational) -> Rational { Rational(remainder(rat), rat.den) } /// Get the fractional part of a mixed fraction. /// Uses floored division. pub fn fraction_floor(rat: Rational) -> Rational { Rational(modulo(rat), rat.den) } /// Rounds the rational upwards. pub fn floor(rat: Rational) -> Rational { case remainder(rat) { 0 -> rat rem -> case is_positive(rat) { True -> Rational(rat.num - rem, rat.den) False -> { Rational(rat.num - rem - rat.den, rat.den) } } } } /// Rounds the rational upwards. pub fn ceiling(rat: Rational) -> Rational { case remainder(rat) { 0 -> rat rem -> case is_positive(rat) { True -> Rational(rat.num - rem + rat.den, rat.den) False -> Rational(rat.num - rem, rat.den) } } } /// [`floor`](#floor) if it is less than 1/2, [`ceiling`](#ceiling) otherwise. pub fn round(rat: Rational) -> Rational { rat |> case compare(rat, Rational(1, 2)) { order.Gt | order.Eq -> ceiling order.Lt -> floor } } pub fn negate(rat: Rational) -> Rational { Rational(-rat.num, rat.den) } pub fn compare(lhs: Rational, rhs: Rational) -> order.Order { int.compare(lhs.num * rhs.den, lhs.den * rhs.num) } pub fn add(lhs: Rational, rhs: Rational) -> Rational { case lhs.den == rhs.den { True -> Rational(lhs.num + rhs.num, lhs.den) False -> Rational({ lhs.num * rhs.den } + { lhs.den * rhs.num }, lhs.den * rhs.den) } } pub fn subtract(lhs: Rational, rhs: Rational) -> Rational { case lhs.den == rhs.den { True -> Rational(lhs.num - rhs.num, lhs.den) False -> Rational({ lhs.num * rhs.den } - { lhs.den * rhs.num }, lhs.den * rhs.den) } } pub fn multiply(lhs: Rational, rhs: Rational) -> Rational { Rational(lhs.num * rhs.num, lhs.den * rhs.den) } /// Also a `new` for rationals. /// /// Returns Error if the second rational's numerator is 0, /// because you are dividing the first rational by zero. pub fn divide_check(lhs: Rational, rhs: Rational) -> Result(Rational, Nil) { flip_check(rhs) |> result.map(multiply(lhs, _)) } /// Also a `new` for rationals. /// /// Returns [`zero`](#zero) if the second rational's numerator is 0 /// because you are dividing the first rational by zero. pub fn divide(lhs: Rational, rhs: Rational) -> Rational { unwrap(divide_check(lhs, rhs)) } /// Returns the largest of two rationals. pub fn max(rat1: Rational, rat2: Rational) -> Rational { // reversed so Eq is rat1 case compare(rat1, rat2) { order.Lt -> rat2 _ -> rat1 } } /// Returns the smallest of two rationals. pub fn min(rat1: Rational, rat2: Rational) -> Rational { // reversed so Eq is rat1 case compare(rat1, rat2) { order.Gt -> rat2 _ -> rat1 } } /// Restricts a rational between a lower and upper bound. pub fn clamp( rat: Rational, min min_bound: Rational, max max_bound: Rational, ) -> Rational { rat |> min(max_bound) |> max(min_bound) } /// Returns Error if the numerator is 0 **and** the exponent is negative, /// because the negative exponent makes it 1/0. pub fn power_check(rat: Rational, of exponent: Int) -> Result(Rational, Nil) { use num <- result.try(int_power_check(rat.num, exponent)) use den <- result.map(int_power_check(rat.den, exponent)) divide(num, den) } /// Returns [`zero`](#zero) if the numerator is 0 **and** the exponent is negative, /// because the negative exponent makes it 1/0. pub fn power(rat: Rational, of exponent: Int) -> Rational { unwrap(power_check(rat, exponent)) } /// Returns Error if the numerator is 0 **and** the exponent is negative, /// because the negative exponent makes it 1/0. pub fn int_power_check(base: Int, of exponent: Int) -> Result(Rational, Nil) { case exponent >= 0 { True -> util.int_power(base, exponent) |> from_int |> Ok False -> new_check(1, util.int_power(base, int.negate(exponent))) } } /// Returns [`zero`](#zero) if the numerator is 0 **and** the exponent is negative, /// because the negative exponent makes it 1/0. pub fn int_power(base: Int, of exponent: Int) -> Rational { unwrap(int_power_check(base, exponent)) } /// Multiplies a list of rationals and returns the product. pub fn product(rats: List(Rational)) -> Rational { list.fold(over: rats, from: one, with: multiply) } /// Sums a list of rationals. pub fn sum(rats: List(Rational)) -> Rational { list.fold(over: rats, from: one, with: add) } /// Splits the numerator and denominator into their digit representations in the specified base. /// Returns an error if the base is less than 2. pub fn digits(rat: Rational, base: Int) -> Result(#(List(Int), List(Int)), Nil) { case int.digits(rat.num, base), int.digits(rat.den, base) { Ok(num), Ok(den) -> Ok(#(num, den)) _, _ -> Error(Nil) } } /// Joins two lists of digits into a rational. /// /// Returns Error if the base is less than 2, the list contains /// a digit greater than or equal to the specified base, /// or the second list of digits equal zero. pub fn undigits_check( tup: #(List(Int), List(Int)), base: Int, ) -> Result(Rational, Nil) { do_undigits(tup, base) |> result.nil_error } /// Joins two lists of digits into a rational. /// /// Returns an error if the base is less than 2, or the list contains /// a digit greater than or equal to the specified base. \ /// Returns [`zero`](#zero) if the second list of digits equal zero. pub fn undigits( tup: #(List(Int), List(Int)), base: Int, ) -> Result(Rational, Nil) { case do_undigits(tup, base) { Ok(rat) -> Ok(rat) Error(ZeroError) -> Ok(zero) _ -> Error(Nil) } } fn do_undigits( tup: #(List(Int), List(Int)), base: Int, ) -> Result(Rational, UndigitsError) { use num <- result.try( int.undigits(tup.0, base) |> result.replace_error(BaseError), ) use den <- result.try( int.undigits(tup.1, base) |> result.replace_error(BaseError), ) new_check(num, den) |> result.replace_error(ZeroError) } type UndigitsError { BaseError ZeroError } pub fn map_num(rat: Rational, fun: fn(Int) -> Int) -> Rational { Rational(fun(rat.num), rat.den) } pub fn map_den_check( rat: Rational, fun: fn(Int) -> Int, ) -> Result(Rational, Nil) { new_check(rat.num, fun(rat.den)) } pub fn map_den(rat: Rational, fun: fn(Int) -> Int) -> Rational { unwrap(map_den_check(rat, fun)) } pub fn map_both_check( rat: Rational, fun: fn(Int) -> Int, ) -> Result(Rational, Nil) { new_check(fun(rat.num), fun(rat.den)) } pub fn map_both(rat: Rational, fun: fn(Int) -> Int) -> Rational { unwrap(map_both_check(rat, fun)) } pub fn map_seperate_check( rat: Rational, fun1: fn(Int) -> Int, fun2: fn(Int) -> Int, ) -> Result(Rational, Nil) { new_check(fun1(rat.num), fun2(rat.den)) } pub fn map_seperate( rat: Rational, fun1: fn(Int) -> Int, fun2: fn(Int) -> Int, ) -> Rational { unwrap(map_seperate_check(rat, fun1, fun2)) } /// Replace error with [`zero`](#zero). pub fn unwrap(res: Result(Rational, Nil)) -> Rational { result.unwrap(res, zero) } /// Internal function to shift negatives up off the denominator. fn shift_negative(rat: Rational) { case rat.den < 0 { True -> Rational(int.negate(rat.num), int.negate(rat.den)) False -> rat } }