2023-03-28 23:55:57 +03:00
|
|
|
// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved.
|
2019-07-03 13:12:36 +03:00
|
|
|
// Use of this source code is governed by an MIT license
|
|
|
|
// that can be found in the LICENSE file.
|
2019-07-15 22:16:41 +03:00
|
|
|
module fractions
|
|
|
|
|
|
|
|
import math
|
2020-05-08 15:39:23 +03:00
|
|
|
import math.bits
|
2019-07-03 13:12:36 +03:00
|
|
|
|
|
|
|
// Fraction Struct
|
2020-05-11 07:20:55 +03:00
|
|
|
// ---------------
|
2020-05-10 17:25:33 +03:00
|
|
|
// A Fraction has a numerator (n) and a denominator (d). If the user uses
|
|
|
|
// the helper functions in this module, then the following are guaranteed:
|
2020-05-11 07:20:55 +03:00
|
|
|
// 1. If the user provides n and d with gcd(n, d) > 1, the fraction will
|
|
|
|
// not be reduced automatically.
|
|
|
|
// 2. d cannot be set to zero. The factory function will panic.
|
|
|
|
// 3. If provided d is negative, it will be made positive. n will change as well.
|
2019-07-03 13:12:36 +03:00
|
|
|
struct Fraction {
|
2020-05-10 17:25:33 +03:00
|
|
|
pub:
|
2021-08-02 18:50:11 +03:00
|
|
|
n i64
|
|
|
|
d i64
|
2020-05-10 17:25:33 +03:00
|
|
|
is_reduced bool
|
2019-07-03 13:12:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// A factory function for creating a Fraction, adds a boundary condition
|
2020-05-10 17:25:33 +03:00
|
|
|
// to ensure that the denominator is non-zero. It automatically converts
|
|
|
|
// the negative denominator to positive and adjusts the numerator.
|
|
|
|
// NOTE: Fractions created are not reduced by default.
|
2020-10-21 12:23:03 +03:00
|
|
|
pub fn fraction(n i64, d i64) Fraction {
|
2020-05-11 07:20:55 +03:00
|
|
|
if d == 0 {
|
2019-07-03 13:12:36 +03:00
|
|
|
panic('Denominator cannot be zero')
|
|
|
|
}
|
2020-05-11 07:20:55 +03:00
|
|
|
// The denominator is always guaranteed to be positive (and non-zero).
|
|
|
|
if d < 0 {
|
|
|
|
return fraction(-n, -d)
|
|
|
|
}
|
|
|
|
return Fraction{
|
|
|
|
n: n
|
|
|
|
d: d
|
|
|
|
is_reduced: math.gcd(n, d) == 1
|
|
|
|
}
|
2019-07-03 13:12:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// To String method
|
2020-05-10 17:25:33 +03:00
|
|
|
pub fn (f Fraction) str() string {
|
2022-11-15 16:53:13 +03:00
|
|
|
return '${f.n}/${f.d}'
|
2019-07-03 13:12:36 +03:00
|
|
|
}
|
|
|
|
|
2020-05-10 17:25:33 +03:00
|
|
|
//
|
|
|
|
// + ---------------------+
|
|
|
|
// | Arithmetic functions.|
|
|
|
|
// + ---------------------+
|
|
|
|
//
|
|
|
|
// These are implemented from Knuth, TAOCP Vol 2. Section 4.5
|
|
|
|
//
|
|
|
|
// Returns a correctly reduced result for both addition and subtraction
|
2020-05-11 07:20:55 +03:00
|
|
|
// NOTE: requires reduced inputs
|
2020-10-21 12:23:03 +03:00
|
|
|
fn general_addition_result(f1 Fraction, f2 Fraction, addition bool) Fraction {
|
2020-05-10 17:25:33 +03:00
|
|
|
d1 := math.gcd(f1.d, f2.d)
|
2021-01-28 11:02:41 +03:00
|
|
|
// d1 happens to be 1 around 600/(pi)^2 or 61 percent of the time (Theorem 4.5.2D)
|
2020-05-10 17:25:33 +03:00
|
|
|
if d1 == 1 {
|
|
|
|
num1n2d := f1.n * f2.d
|
|
|
|
num1d2n := f1.d * f2.n
|
2020-05-11 07:20:55 +03:00
|
|
|
n := if addition { num1n2d + num1d2n } else { num1n2d - num1d2n }
|
2020-05-10 17:25:33 +03:00
|
|
|
return Fraction{
|
|
|
|
n: n
|
|
|
|
d: f1.d * f2.d
|
|
|
|
is_reduced: true
|
|
|
|
}
|
2019-07-03 13:12:36 +03:00
|
|
|
}
|
2020-05-10 17:25:33 +03:00
|
|
|
// Here d1 > 1.
|
2020-05-11 07:20:55 +03:00
|
|
|
f1den := f1.d / d1
|
|
|
|
f2den := f2.d / d1
|
|
|
|
term1 := f1.n * f2den
|
|
|
|
term2 := f2.n * f1den
|
|
|
|
t := if addition { term1 + term2 } else { term1 - term2 }
|
2020-05-10 17:25:33 +03:00
|
|
|
d2 := math.gcd(t, d1)
|
|
|
|
return Fraction{
|
|
|
|
n: t / d2
|
2020-05-11 07:20:55 +03:00
|
|
|
d: f1den * (f2.d / d2)
|
2020-05-10 17:25:33 +03:00
|
|
|
is_reduced: true
|
2019-07-03 13:12:36 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-10 17:25:33 +03:00
|
|
|
// Fraction add using operator overloading
|
2021-02-20 21:42:55 +03:00
|
|
|
pub fn (f1 Fraction) + (f2 Fraction) Fraction {
|
2020-05-10 17:25:33 +03:00
|
|
|
return general_addition_result(f1.reduce(), f2.reduce(), true)
|
|
|
|
}
|
|
|
|
|
2019-07-12 21:45:56 +03:00
|
|
|
// Fraction subtract using operator overloading
|
2021-02-20 21:42:55 +03:00
|
|
|
pub fn (f1 Fraction) - (f2 Fraction) Fraction {
|
2020-05-10 17:25:33 +03:00
|
|
|
return general_addition_result(f1.reduce(), f2.reduce(), false)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns a correctly reduced result for both multiplication and division
|
2020-05-11 07:20:55 +03:00
|
|
|
// NOTE: requires reduced inputs
|
2020-10-21 12:23:03 +03:00
|
|
|
fn general_multiplication_result(f1 Fraction, f2 Fraction, multiplication bool) Fraction {
|
2020-05-11 07:20:55 +03:00
|
|
|
// * Theorem: If f1 and f2 are reduced i.e. gcd(f1.n, f1.d) == 1 and gcd(f2.n, f2.d) == 1,
|
2020-05-10 17:25:33 +03:00
|
|
|
// then gcd(f1.n * f2.n, f1.d * f2.d) == gcd(f1.n, f2.d) * gcd(f1.d, f2.n)
|
2020-05-11 07:20:55 +03:00
|
|
|
// * Knuth poses this an exercise for 4.5.1. - Exercise 2
|
|
|
|
// * Also, note that:
|
2020-05-10 17:25:33 +03:00
|
|
|
// The terms are flipped for multiplication and division, so the gcds must be calculated carefully
|
2020-05-11 07:20:55 +03:00
|
|
|
// We do multiple divisions in order to prevent any possible overflows.
|
|
|
|
// * One more thing:
|
2020-05-10 17:25:33 +03:00
|
|
|
// if d = gcd(a, b) for example, then d divides both a and b
|
|
|
|
if multiplication {
|
2020-05-11 07:20:55 +03:00
|
|
|
d1 := math.gcd(f1.n, f2.d)
|
|
|
|
d2 := math.gcd(f1.d, f2.n)
|
|
|
|
return Fraction{
|
|
|
|
n: (f1.n / d1) * (f2.n / d2)
|
|
|
|
d: (f2.d / d1) * (f1.d / d2)
|
|
|
|
is_reduced: true
|
|
|
|
}
|
2020-05-10 17:25:33 +03:00
|
|
|
} else {
|
2020-05-11 07:20:55 +03:00
|
|
|
d1 := math.gcd(f1.n, f2.n)
|
|
|
|
d2 := math.gcd(f1.d, f2.d)
|
|
|
|
return Fraction{
|
|
|
|
n: (f1.n / d1) * (f2.d / d2)
|
|
|
|
d: (f2.n / d1) * (f1.d / d2)
|
|
|
|
is_reduced: true
|
|
|
|
}
|
2019-07-03 13:12:36 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fraction multiply using operator overloading
|
2021-02-20 21:42:55 +03:00
|
|
|
pub fn (f1 Fraction) * (f2 Fraction) Fraction {
|
2020-05-10 17:25:33 +03:00
|
|
|
return general_multiplication_result(f1.reduce(), f2.reduce(), true)
|
|
|
|
}
|
2019-07-03 13:12:36 +03:00
|
|
|
|
|
|
|
// Fraction divide using operator overloading
|
2021-02-20 21:42:55 +03:00
|
|
|
pub fn (f1 Fraction) / (f2 Fraction) Fraction {
|
2020-05-10 17:25:33 +03:00
|
|
|
if f2.n == 0 {
|
2021-01-24 11:37:00 +03:00
|
|
|
panic('Cannot divide by zero')
|
2020-05-10 17:25:33 +03:00
|
|
|
}
|
|
|
|
// If the second fraction is negative, it will
|
|
|
|
// mess up the sign. We need positive denominator
|
|
|
|
if f2.n < 0 {
|
|
|
|
return f1.negate() / f2.negate()
|
|
|
|
}
|
|
|
|
return general_multiplication_result(f1.reduce(), f2.reduce(), false)
|
|
|
|
}
|
2019-07-03 13:12:36 +03:00
|
|
|
|
2020-05-10 17:25:33 +03:00
|
|
|
// Fraction negate method
|
2020-05-13 19:39:19 +03:00
|
|
|
pub fn (f Fraction) negate() Fraction {
|
2020-05-10 17:25:33 +03:00
|
|
|
return Fraction{
|
2020-05-13 19:39:19 +03:00
|
|
|
n: -f.n
|
|
|
|
d: f.d
|
|
|
|
is_reduced: f.is_reduced
|
2020-05-10 17:25:33 +03:00
|
|
|
}
|
2019-07-03 13:12:36 +03:00
|
|
|
}
|
|
|
|
|
2020-05-10 17:25:33 +03:00
|
|
|
// Fraction reciprocal method
|
2020-05-13 19:39:19 +03:00
|
|
|
pub fn (f Fraction) reciprocal() Fraction {
|
|
|
|
if f.n == 0 {
|
2020-05-10 17:25:33 +03:00
|
|
|
panic('Denominator cannot be zero')
|
|
|
|
}
|
|
|
|
return Fraction{
|
2020-05-13 19:39:19 +03:00
|
|
|
n: f.d
|
|
|
|
d: f.n
|
|
|
|
is_reduced: f.is_reduced
|
2020-05-10 17:25:33 +03:00
|
|
|
}
|
2019-07-03 13:12:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Fraction method which reduces the fraction
|
2020-05-13 19:39:19 +03:00
|
|
|
pub fn (f Fraction) reduce() Fraction {
|
|
|
|
if f.is_reduced {
|
|
|
|
return f
|
2020-05-10 17:25:33 +03:00
|
|
|
}
|
2020-05-13 19:39:19 +03:00
|
|
|
cf := math.gcd(f.n, f.d)
|
2020-05-10 17:25:33 +03:00
|
|
|
return Fraction{
|
2020-05-13 19:39:19 +03:00
|
|
|
n: f.n / cf
|
|
|
|
d: f.d / cf
|
2020-05-10 17:25:33 +03:00
|
|
|
is_reduced: true
|
|
|
|
}
|
2019-07-03 13:12:36 +03:00
|
|
|
}
|
|
|
|
|
2020-05-10 17:25:33 +03:00
|
|
|
// f64 converts the Fraction to 64-bit floating point
|
2020-05-13 19:39:19 +03:00
|
|
|
pub fn (f Fraction) f64() f64 {
|
|
|
|
return f64(f.n) / f64(f.d)
|
2019-07-03 13:12:36 +03:00
|
|
|
}
|
|
|
|
|
2020-05-10 17:25:33 +03:00
|
|
|
//
|
|
|
|
// + ------------------+
|
|
|
|
// | Utility functions.|
|
|
|
|
// + ------------------+
|
|
|
|
//
|
2020-05-08 15:39:23 +03:00
|
|
|
// Returns the absolute value of an i64
|
|
|
|
fn abs(num i64) i64 {
|
|
|
|
if num < 0 {
|
|
|
|
return -num
|
|
|
|
} else {
|
|
|
|
return num
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-05 13:23:18 +03:00
|
|
|
// cmp_i64s compares the two arguments, returns 0 when equal, 1 when the first is bigger, -1 otherwise
|
2020-10-21 12:23:03 +03:00
|
|
|
fn cmp_i64s(a i64, b i64) int {
|
2020-05-10 17:25:33 +03:00
|
|
|
if a == b {
|
|
|
|
return 0
|
|
|
|
} else if a > b {
|
|
|
|
return 1
|
|
|
|
} else {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-05 13:23:18 +03:00
|
|
|
// cmp_f64s compares the two arguments, returns 0 when equal, 1 when the first is bigger, -1 otherwise
|
2020-10-21 12:23:03 +03:00
|
|
|
fn cmp_f64s(a f64, b f64) int {
|
2020-05-10 17:25:33 +03:00
|
|
|
// V uses epsilon comparison internally
|
|
|
|
if a == b {
|
|
|
|
return 0
|
|
|
|
} else if a > b {
|
|
|
|
return 1
|
|
|
|
} else {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-08 15:39:23 +03:00
|
|
|
// Two integers are safe to multiply when their bit lengths
|
|
|
|
// sum up to less than 64 (conservative estimate).
|
2020-10-21 12:23:03 +03:00
|
|
|
fn safe_to_multiply(a i64, b i64) bool {
|
2020-06-01 22:15:59 +03:00
|
|
|
return (bits.len_64(u64(abs(a))) + bits.len_64(u64(abs(b)))) < 64
|
2020-05-08 15:39:23 +03:00
|
|
|
}
|
|
|
|
|
2020-12-05 13:23:18 +03:00
|
|
|
// cmp compares the two arguments, returns 0 when equal, 1 when the first is bigger, -1 otherwise
|
2020-10-21 12:23:03 +03:00
|
|
|
fn cmp(f1 Fraction, f2 Fraction) int {
|
2020-05-08 15:39:23 +03:00
|
|
|
if safe_to_multiply(f1.n, f2.d) && safe_to_multiply(f2.n, f1.d) {
|
2020-05-10 17:25:33 +03:00
|
|
|
return cmp_i64s(f1.n * f2.d, f2.n * f1.d)
|
|
|
|
} else {
|
|
|
|
return cmp_f64s(f1.f64(), f2.f64())
|
2020-05-08 15:39:23 +03:00
|
|
|
}
|
2020-05-10 17:25:33 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// +-----------------------------+
|
|
|
|
// | Public comparison functions |
|
|
|
|
// +-----------------------------+
|
|
|
|
// equals returns true if both the Fractions are equal
|
|
|
|
pub fn (f1 Fraction) equals(f2 Fraction) bool {
|
|
|
|
return cmp(f1, f2) == 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// ge returns true if f1 >= f2
|
|
|
|
pub fn (f1 Fraction) ge(f2 Fraction) bool {
|
|
|
|
return cmp(f1, f2) >= 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// gt returns true if f1 > f2
|
|
|
|
pub fn (f1 Fraction) gt(f2 Fraction) bool {
|
|
|
|
return cmp(f1, f2) > 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// le returns true if f1 <= f2
|
|
|
|
pub fn (f1 Fraction) le(f2 Fraction) bool {
|
|
|
|
return cmp(f1, f2) <= 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// lt returns true if f1 < f2
|
|
|
|
pub fn (f1 Fraction) lt(f2 Fraction) bool {
|
|
|
|
return cmp(f1, f2) < 0
|
2019-07-10 14:51:48 +03:00
|
|
|
}
|