260 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			V
		
	
	
			
		
		
	
	
			260 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			V
		
	
	
| // Copyright (c) 2019-2021 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. 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.
 | |
| 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 i64, d i64) Fraction {
 | |
| 	if d == 0 {
 | |
| 		panic('Denominator cannot be zero')
 | |
| 	}
 | |
| 	// 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
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // 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
 | |
| // NOTE: requires reduced inputs
 | |
| fn general_addition_result(f1 Fraction, f2 Fraction, addition bool) Fraction {
 | |
| 	d1 := math.gcd(f1.d, f2.d)
 | |
| 	// d1 happens to be 1 around 600/(pi)^2 or 61 percent of the time (Theorem 4.5.2D)
 | |
| 	if d1 == 1 {
 | |
| 		num1n2d := f1.n * f2.d
 | |
| 		num1d2n := f1.d * f2.n
 | |
| 		n := if addition { num1n2d + num1d2n } else { num1n2d - num1d2n }
 | |
| 		return Fraction{
 | |
| 			n: n
 | |
| 			d: f1.d * f2.d
 | |
| 			is_reduced: true
 | |
| 		}
 | |
| 	}
 | |
| 	// Here d1 > 1.
 | |
| 	f1den := f1.d / d1
 | |
| 	f2den := f2.d / d1
 | |
| 	term1 := f1.n * f2den
 | |
| 	term2 := f2.n * f1den
 | |
| 	t := if addition { term1 + term2 } else { term1 - term2 }
 | |
| 	d2 := math.gcd(t, d1)
 | |
| 	return Fraction{
 | |
| 		n: t / d2
 | |
| 		d: f1den * (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
 | |
| // NOTE: requires reduced inputs
 | |
| fn general_multiplication_result(f1 Fraction, 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
 | |
| 	// * Also, note that:
 | |
| 	// 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.
 | |
| 	// * One more thing:
 | |
| 	// 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)
 | |
| 		return Fraction{
 | |
| 			n: (f1.n / d1) * (f2.n / d2)
 | |
| 			d: (f2.d / d1) * (f1.d / d2)
 | |
| 			is_reduced: true
 | |
| 		}
 | |
| 	} else {
 | |
| 		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
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // 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 divide 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 negate method
 | |
| pub fn (f Fraction) negate() Fraction {
 | |
| 	return Fraction{
 | |
| 		n: -f.n
 | |
| 		d: f.d
 | |
| 		is_reduced: f.is_reduced
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Fraction reciprocal method
 | |
| pub fn (f Fraction) reciprocal() Fraction {
 | |
| 	if f.n == 0 {
 | |
| 		panic('Denominator cannot be zero')
 | |
| 	}
 | |
| 	return Fraction{
 | |
| 		n: f.d
 | |
| 		d: f.n
 | |
| 		is_reduced: f.is_reduced
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Fraction method which reduces the fraction
 | |
| pub fn (f Fraction) reduce() Fraction {
 | |
| 	if f.is_reduced {
 | |
| 		return f
 | |
| 	}
 | |
| 	cf := math.gcd(f.n, f.d)
 | |
| 	return Fraction{
 | |
| 		n: f.n / cf
 | |
| 		d: f.d / cf
 | |
| 		is_reduced: true
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // f64 converts the Fraction to 64-bit floating point
 | |
| pub fn (f Fraction) f64() f64 {
 | |
| 	return f64(f.n) / f64(f.d)
 | |
| }
 | |
| 
 | |
| //
 | |
| // + ------------------+
 | |
| // | Utility functions.|
 | |
| // + ------------------+
 | |
| //
 | |
| // Returns the absolute value of an i64
 | |
| fn abs(num i64) i64 {
 | |
| 	if num < 0 {
 | |
| 		return -num
 | |
| 	} else {
 | |
| 		return num
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // cmp_i64s compares the two arguments, returns 0 when equal, 1 when the first is bigger, -1 otherwise
 | |
| fn cmp_i64s(a i64, b i64) int {
 | |
| 	if a == b {
 | |
| 		return 0
 | |
| 	} else if a > b {
 | |
| 		return 1
 | |
| 	} else {
 | |
| 		return -1
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // cmp_f64s compares the two arguments, returns 0 when equal, 1 when the first is bigger, -1 otherwise
 | |
| fn cmp_f64s(a f64, 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 i64, b i64) bool {
 | |
| 	return (bits.len_64(u64(abs(a))) + bits.len_64(u64(abs(b)))) < 64
 | |
| }
 | |
| 
 | |
| // cmp compares the two arguments, returns 0 when equal, 1 when the first is bigger, -1 otherwise
 | |
| fn cmp(f1 Fraction, 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
 | |
| }
 |