v/vlib/math/fractions/fraction.v

288 lines
6.7 KiB
V

// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module fractions
import math
import math.bits
// Fraction Struct
// 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:
// 1.
struct Fraction {
n i64
d i64
pub:
is_reduced bool
}
// A factory function for creating a Fraction, adds a boundary condition
// 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.
pub fn fraction(n, d i64) Fraction {
if d != 0 {
// The denominator is always guaranteed to be positive (and non-zero).
if d < 0 {
return fraction(-n, -d)
} else {
return Fraction{
n: n
d: d
is_reduced: math.gcd(n, d) == 1
}
}
} else {
panic('Denominator cannot be zero')
}
}
// To String method
pub fn (f Fraction) str() string {
return '$f.n/$f.d'
}
//
// + ---------------------+
// | Arithmetic functions.|
// + ---------------------+
//
// These are implemented from Knuth, TAOCP Vol 2. Section 4.5
//
// Returns a correctly reduced result for both addition and subtraction
fn general_addition_result(f1, f2 Fraction, addition bool) Fraction {
d1 := math.gcd(f1.d, f2.d)
// d1 happends to be 1 around 600/(pi)^2 or 61 percent of the time (Theorem 4.5.2D)
if d1 == 1 {
mut n := i64(0)
num1n2d := f1.n * f2.d
num1d2n := f1.d * f2.n
if addition {
n = num1n2d + num1d2n
} else {
n = num1n2d - num1d2n
}
return Fraction{
n: n
d: f1.d * f2.d
is_reduced: true
}
}
// Here d1 > 1.
// Without the i64(...), t is declared as an int
// and it does not have enough precision
mut t := i64(0)
term1 := f1.n * (f2.d / d1)
term2 := f2.n * (f1.d / d1)
if addition {
t = term1 + term2
} else {
t = term1 - term2
}
d2 := math.gcd(t, d1)
return Fraction{
n: t / d2
d: (f1.d / d1) * (f2.d / d2)
is_reduced: true
}
}
// Fraction add using operator overloading
pub fn (f1 Fraction) +(f2 Fraction) Fraction {
return general_addition_result(f1.reduce(), f2.reduce(), true)
}
// Fraction subtract using operator overloading
pub fn (f1 Fraction) -(f2 Fraction) Fraction {
return general_addition_result(f1.reduce(), f2.reduce(), false)
}
// Returns a correctly reduced result for both multiplication and division
fn general_multiplication_result(f1, f2 Fraction, multiplication bool) Fraction {
// Theorem: If f1 and f2 are reduced i.e. gcd(f1.n, f1.d) == 1 and gcd(f2.n, f2.d) == 1,
// then gcd(f1.n * f2.n, f1.d * f2.d) == gcd(f1.n, f2.d) * gcd(f1.d, f2.n)
// Knuth poses this an exercise for 4.5.1. - Exercise 2
mut d1 := i64(0)
mut d2 := i64(0)
mut n := i64(0)
mut d := i64(0)
// The terms are flipped for multiplication and division, so the gcds must be calculated carefully
// We do multiple divisions in order to prevent any possible overflows. Also, note that:
// if d = gcd(a, b) for example, then d divides both a and b
if multiplication {
d1 = math.gcd(f1.n, f2.d)
d2 = math.gcd(f1.d, f2.n)
n = (f1.n / d1) * (f2.n / d2)
d = (f2.d / d1) * (f1.d / d2)
} else {
d1 = math.gcd(f1.n, f2.n)
d2 = math.gcd(f1.d, f2.d)
n = (f1.n / d1) * (f2.d / d2)
d = (f2.n / d1) * (f1.d / d2)
}
return Fraction{
n: n
d: d
is_reduced: true
}
}
// Fraction multiply using operator overloading
pub fn (f1 Fraction) *(f2 Fraction) Fraction {
return general_multiplication_result(f1.reduce(), f2.reduce(), true)
}
// Fraction divide using operator overloading
pub fn (f1 Fraction) /(f2 Fraction) Fraction {
if f2.n == 0 {
panic('Cannot divive by zero')
}
// 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)
}
// Fraction add method. Deprecated. Use the operator instead.
[deprecated]
pub fn (f1 Fraction) add(f2 Fraction) Fraction {
return f1 + f2
}
// Fraction subtract method. Deprecated. Use the operator instead.
[deprecated]
pub fn (f1 Fraction) subtract(f2 Fraction) Fraction {
return f1 - f2
}
// Fraction multiply method. Deprecated. Use the operator instead.
[deprecated]
pub fn (f1 Fraction) multiply(f2 Fraction) Fraction {
return f1 * f2
}
// Fraction divide method. Deprecated. Use the operator instead.
[deprecated]
pub fn (f1 Fraction) divide(f2 Fraction) Fraction {
return f1 / f2
}
// Fraction negate method
pub fn (f1 Fraction) negate() Fraction {
return Fraction{
n: -f1.n
d: f1.d
is_reduced: f1.is_reduced
}
}
// Fraction reciprocal method
pub fn (f1 Fraction) reciprocal() Fraction {
if f1.n == 0 {
panic('Denominator cannot be zero')
}
return Fraction{
n: f1.d
d: f1.n
is_reduced: f1.is_reduced
}
}
// Fraction method which reduces the fraction
pub fn (f1 Fraction) reduce() Fraction {
if f1.is_reduced {
return f1
}
cf := math.gcd(f1.n, f1.d)
return Fraction{
n: f1.n / cf
d: f1.d / cf
is_reduced: true
}
}
// f64 converts the Fraction to 64-bit floating point
pub fn (f1 Fraction) f64() f64 {
return f64(f1.n) / f64(f1.d)
}
//
// + ------------------+
// | Utility functions.|
// + ------------------+
//
// Returns the absolute value of an i64
fn abs(num i64) i64 {
if num < 0 {
return -num
} else {
return num
}
}
fn cmp_i64s(a, b i64) int {
if a == b {
return 0
} else if a > b {
return 1
} else {
return -1
}
}
fn cmp_f64s(a, b f64) int {
// V uses epsilon comparison internally
if a == b {
return 0
} else if a > b {
return 1
} else {
return -1
}
}
// Two integers are safe to multiply when their bit lengths
// sum up to less than 64 (conservative estimate).
fn safe_to_multiply(a, b i64) bool {
return (bits.len_64(abs(a)) + bits.len_64(abs(b))) < 64
}
fn cmp(f1, f2 Fraction) int {
if safe_to_multiply(f1.n, f2.d) && safe_to_multiply(f2.n, f1.d) {
return cmp_i64s(f1.n * f2.d, f2.n * f1.d)
} else {
return cmp_f64s(f1.f64(), f2.f64())
}
}
// +-----------------------------+
// | 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
}