aboutsummaryrefslogtreecommitdiff
path: root/v_windows/v/vlib/math/fractions
diff options
context:
space:
mode:
authorIndrajith K L2022-12-03 17:00:20 +0530
committerIndrajith K L2022-12-03 17:00:20 +0530
commitf5c4671bfbad96bf346bd7e9a21fc4317b4959df (patch)
tree2764fc62da58f2ba8da7ed341643fc359873142f /v_windows/v/vlib/math/fractions
downloadcli-tools-windows-master.tar.gz
cli-tools-windows-master.tar.bz2
cli-tools-windows-master.zip
Adds most of the toolsHEADmaster
Diffstat (limited to 'v_windows/v/vlib/math/fractions')
-rw-r--r--v_windows/v/vlib/math/fractions/approximations.v119
-rw-r--r--v_windows/v/vlib/math/fractions/approximations_test.v189
-rw-r--r--v_windows/v/vlib/math/fractions/fraction.v259
-rw-r--r--v_windows/v/vlib/math/fractions/fraction_test.v269
4 files changed, 836 insertions, 0 deletions
diff --git a/v_windows/v/vlib/math/fractions/approximations.v b/v_windows/v/vlib/math/fractions/approximations.v
new file mode 100644
index 0000000..dd4f855
--- /dev/null
+++ b/v_windows/v/vlib/math/fractions/approximations.v
@@ -0,0 +1,119 @@
+// 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
+
+const (
+ default_eps = 1.0e-4
+ max_iterations = 50
+ zero = fraction(0, 1)
+)
+
+// ------------------------------------------------------------------------
+// Unwrapped evaluation methods for fast evaluation of continued fractions.
+// ------------------------------------------------------------------------
+// We need these functions because the evaluation of continued fractions
+// always has to be done from the end. Also, the numerator-denominator pairs
+// are generated from front to end. This means building a result from a
+// previous one isn't possible. So we need unrolled versions to ensure that
+// we don't take too much of a performance penalty by calling eval_cf
+// several times.
+// ------------------------------------------------------------------------
+// eval_1 returns the result of evaluating a continued fraction series of length 1
+fn eval_1(whole i64, d []i64) Fraction {
+ return fraction(whole * d[0] + 1, d[0])
+}
+
+// eval_2 returns the result of evaluating a continued fraction series of length 2
+fn eval_2(whole i64, d []i64) Fraction {
+ den := d[0] * d[1] + 1
+ return fraction(whole * den + d[1], den)
+}
+
+// eval_3 returns the result of evaluating a continued fraction series of length 3
+fn eval_3(whole i64, d []i64) Fraction {
+ d1d2_plus_n2 := d[1] * d[2] + 1
+ den := d[0] * d1d2_plus_n2 + d[2]
+ return fraction(whole * den + d1d2_plus_n2, den)
+}
+
+// eval_cf evaluates a continued fraction series and returns a Fraction.
+fn eval_cf(whole i64, den []i64) Fraction {
+ count := den.len
+ // Offload some small-scale calculations
+ // to dedicated functions
+ match count {
+ 1 {
+ return eval_1(whole, den)
+ }
+ 2 {
+ return eval_2(whole, den)
+ }
+ 3 {
+ return eval_3(whole, den)
+ }
+ else {
+ last := count - 1
+ mut n := i64(1)
+ mut d := den[last]
+ // The calculations are done from back to front
+ for index := count - 2; index >= 0; index-- {
+ t := d
+ d = den[index] * d + n
+ n = t
+ }
+ return fraction(d * whole + n, d)
+ }
+ }
+}
+
+// approximate returns a Fraction that approcimates the given value to
+// within the default epsilon value (1.0e-4). This means the result will
+// be accurate to 3 places after the decimal.
+pub fn approximate(val f64) Fraction {
+ return approximate_with_eps(val, fractions.default_eps)
+}
+
+// approximate_with_eps returns a Fraction
+pub fn approximate_with_eps(val f64, eps f64) Fraction {
+ if val == 0.0 {
+ return fractions.zero
+ }
+ if eps < 0.0 {
+ panic('Epsilon value cannot be negative.')
+ }
+ if math.fabs(val) > math.max_i64 {
+ panic('Value out of range.')
+ }
+ // The integer part is separated first. Then we process the fractional
+ // part to generate numerators and denominators in tandem.
+ whole := i64(val)
+ mut frac := val - f64(whole)
+ // Quick exit for integers
+ if frac == 0.0 {
+ return fraction(whole, 1)
+ }
+ mut d := []i64{}
+ mut partial := fractions.zero
+ // We must complete the approximation within the maximum number of
+ // itertations allowed. If we can't panic.
+ // Empirically tested: the hardest constant to approximate is the
+ // golden ratio (math.phi) and for f64s, it only needs 38 iterations.
+ for _ in 0 .. fractions.max_iterations {
+ // We calculate the reciprocal. That's why the numerator is
+ // always 1.
+ frac = 1.0 / frac
+ den := i64(frac)
+ d << den
+ // eval_cf is called often so it needs to be performant
+ partial = eval_cf(whole, d)
+ // Check if we're done
+ if math.fabs(val - partial.f64()) < eps {
+ return partial
+ }
+ frac -= f64(den)
+ }
+ panic("Couldn't converge. Please create an issue on https://github.com/vlang/v")
+}
diff --git a/v_windows/v/vlib/math/fractions/approximations_test.v b/v_windows/v/vlib/math/fractions/approximations_test.v
new file mode 100644
index 0000000..5ee92bf
--- /dev/null
+++ b/v_windows/v/vlib/math/fractions/approximations_test.v
@@ -0,0 +1,189 @@
+// 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.
+import math.fractions
+import math
+
+fn test_half() {
+ float_val := 0.5
+ fract_val := fractions.approximate(float_val)
+ assert fract_val.equals(fractions.fraction(1, 2))
+}
+
+fn test_third() {
+ float_val := 1.0 / 3.0
+ fract_val := fractions.approximate(float_val)
+ assert fract_val.equals(fractions.fraction(1, 3))
+}
+
+fn test_minus_one_twelfth() {
+ float_val := -1.0 / 12.0
+ fract_val := fractions.approximate(float_val)
+ assert fract_val.equals(fractions.fraction(-1, 12))
+}
+
+fn test_zero() {
+ float_val := 0.0
+ println('Pre')
+ fract_val := fractions.approximate(float_val)
+ println('Post')
+ assert fract_val.equals(fractions.fraction(0, 1))
+}
+
+fn test_minus_one() {
+ float_val := -1.0
+ fract_val := fractions.approximate(float_val)
+ assert fract_val.equals(fractions.fraction(-1, 1))
+}
+
+fn test_thirty_three() {
+ float_val := 33.0
+ fract_val := fractions.approximate(float_val)
+ assert fract_val.equals(fractions.fraction(33, 1))
+}
+
+fn test_millionth() {
+ float_val := 1.0 / 1000000.0
+ fract_val := fractions.approximate(float_val)
+ assert fract_val.equals(fractions.fraction(1, 1000000))
+}
+
+fn test_minus_27_by_57() {
+ float_val := -27.0 / 57.0
+ fract_val := fractions.approximate(float_val)
+ assert fract_val.equals(fractions.fraction(-27, 57))
+}
+
+fn test_29_by_104() {
+ float_val := 29.0 / 104.0
+ fract_val := fractions.approximate(float_val)
+ assert fract_val.equals(fractions.fraction(29, 104))
+}
+
+fn test_140710_232() {
+ float_val := 140710.232
+ fract_val := fractions.approximate(float_val)
+ // Approximation will match perfectly for upto 3 places after the decimal
+ // The result will be within default_eps of original value
+ assert fract_val.f64() == float_val
+}
+
+fn test_pi_1_digit() {
+ assert fractions.approximate_with_eps(math.pi, 5.0e-2).equals(fractions.fraction(22,
+ 7))
+}
+
+fn test_pi_2_digits() {
+ assert fractions.approximate_with_eps(math.pi, 5.0e-3).equals(fractions.fraction(22,
+ 7))
+}
+
+fn test_pi_3_digits() {
+ assert fractions.approximate_with_eps(math.pi, 5.0e-4).equals(fractions.fraction(333,
+ 106))
+}
+
+fn test_pi_4_digits() {
+ assert fractions.approximate_with_eps(math.pi, 5.0e-5).equals(fractions.fraction(355,
+ 113))
+}
+
+fn test_pi_5_digits() {
+ assert fractions.approximate_with_eps(math.pi, 5.0e-6).equals(fractions.fraction(355,
+ 113))
+}
+
+fn test_pi_6_digits() {
+ assert fractions.approximate_with_eps(math.pi, 5.0e-7).equals(fractions.fraction(355,
+ 113))
+}
+
+fn test_pi_7_digits() {
+ assert fractions.approximate_with_eps(math.pi, 5.0e-8).equals(fractions.fraction(103993,
+ 33102))
+}
+
+fn test_pi_8_digits() {
+ assert fractions.approximate_with_eps(math.pi, 5.0e-9).equals(fractions.fraction(103993,
+ 33102))
+}
+
+fn test_pi_9_digits() {
+ assert fractions.approximate_with_eps(math.pi, 5.0e-10).equals(fractions.fraction(104348,
+ 33215))
+}
+
+fn test_pi_10_digits() {
+ assert fractions.approximate_with_eps(math.pi, 5.0e-11).equals(fractions.fraction(312689,
+ 99532))
+}
+
+fn test_pi_11_digits() {
+ assert fractions.approximate_with_eps(math.pi, 5.0e-12).equals(fractions.fraction(1146408,
+ 364913))
+}
+
+fn test_pi_12_digits() {
+ assert fractions.approximate_with_eps(math.pi, 5.0e-13).equals(fractions.fraction(4272943,
+ 1360120))
+}
+
+fn test_phi_1_digit() {
+ assert fractions.approximate_with_eps(math.phi, 5.0e-2).equals(fractions.fraction(5,
+ 3))
+}
+
+fn test_phi_2_digits() {
+ assert fractions.approximate_with_eps(math.phi, 5.0e-3).equals(fractions.fraction(21,
+ 13))
+}
+
+fn test_phi_3_digits() {
+ assert fractions.approximate_with_eps(math.phi, 5.0e-4).equals(fractions.fraction(55,
+ 34))
+}
+
+fn test_phi_4_digits() {
+ assert fractions.approximate_with_eps(math.phi, 5.0e-5).equals(fractions.fraction(233,
+ 144))
+}
+
+fn test_phi_5_digits() {
+ assert fractions.approximate_with_eps(math.phi, 5.0e-6).equals(fractions.fraction(610,
+ 377))
+}
+
+fn test_phi_6_digits() {
+ assert fractions.approximate_with_eps(math.phi, 5.0e-7).equals(fractions.fraction(1597,
+ 987))
+}
+
+fn test_phi_7_digits() {
+ assert fractions.approximate_with_eps(math.phi, 5.0e-8).equals(fractions.fraction(6765,
+ 4181))
+}
+
+fn test_phi_8_digits() {
+ assert fractions.approximate_with_eps(math.phi, 5.0e-9).equals(fractions.fraction(17711,
+ 10946))
+}
+
+fn test_phi_9_digits() {
+ assert fractions.approximate_with_eps(math.phi, 5.0e-10).equals(fractions.fraction(75025,
+ 46368))
+}
+
+fn test_phi_10_digits() {
+ assert fractions.approximate_with_eps(math.phi, 5.0e-11).equals(fractions.fraction(196418,
+ 121393))
+}
+
+fn test_phi_11_digits() {
+ assert fractions.approximate_with_eps(math.phi, 5.0e-12).equals(fractions.fraction(514229,
+ 317811))
+}
+
+fn test_phi_12_digits() {
+ assert fractions.approximate_with_eps(math.phi, 5.0e-13).equals(fractions.fraction(2178309,
+ 1346269))
+}
diff --git a/v_windows/v/vlib/math/fractions/fraction.v b/v_windows/v/vlib/math/fractions/fraction.v
new file mode 100644
index 0000000..69c8f63
--- /dev/null
+++ b/v_windows/v/vlib/math/fractions/fraction.v
@@ -0,0 +1,259 @@
+// 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 {
+pub:
+ n i64
+ d i64
+ 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
+}
diff --git a/v_windows/v/vlib/math/fractions/fraction_test.v b/v_windows/v/vlib/math/fractions/fraction_test.v
new file mode 100644
index 0000000..4928b7c
--- /dev/null
+++ b/v_windows/v/vlib/math/fractions/fraction_test.v
@@ -0,0 +1,269 @@
+// 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.
+import math.fractions
+
+// (Old) results are verified using https://www.calculatorsoup.com/calculators/math/fractions.php
+// Newer ones are contrived for corner cases or prepared by hand.
+fn test_4_by_8_f64_and_str() {
+ f := fractions.fraction(4, 8)
+ assert f.f64() == 0.5
+ assert f.str() == '4/8'
+}
+
+fn test_10_by_5_f64_and_str() {
+ f := fractions.fraction(10, 5)
+ assert f.f64() == 2.0
+ assert f.str() == '10/5'
+}
+
+fn test_9_by_3_f64_and_str() {
+ f := fractions.fraction(9, 3)
+ assert f.f64() == 3.0
+ assert f.str() == '9/3'
+}
+
+fn test_4_by_minus_5_f64_and_str() {
+ f := fractions.fraction(4, -5)
+ assert f.f64() == -0.8
+ assert f.str() == '-4/5'
+}
+
+fn test_minus_7_by_minus_92_str() {
+ f := fractions.fraction(-7, -5)
+ assert f.str() == '7/5'
+}
+
+fn test_4_by_8_plus_5_by_10() {
+ f1 := fractions.fraction(4, 8)
+ f2 := fractions.fraction(5, 10)
+ sum := f1 + f2
+ assert sum.f64() == 1.0
+ assert sum.str() == '1/1'
+ assert sum.equals(fractions.fraction(1, 1))
+}
+
+fn test_5_by_5_plus_8_by_8() {
+ f1 := fractions.fraction(5, 5)
+ f2 := fractions.fraction(8, 8)
+ sum := f1 + f2
+ assert sum.f64() == 2.0
+ assert sum.str() == '2/1'
+ assert sum.equals(fractions.fraction(2, 1))
+}
+
+fn test_9_by_3_plus_1_by_3() {
+ f1 := fractions.fraction(9, 3)
+ f2 := fractions.fraction(1, 3)
+ sum := f1 + f2
+ assert sum.str() == '10/3'
+ assert sum.equals(fractions.fraction(10, 3))
+}
+
+fn test_3_by_7_plus_1_by_4() {
+ f1 := fractions.fraction(3, 7)
+ f2 := fractions.fraction(1, 4)
+ sum := f1 + f2
+ assert sum.str() == '19/28'
+ assert sum.equals(fractions.fraction(19, 28))
+}
+
+fn test_36529_by_12409100000_plus_418754901_by_9174901000() {
+ f1 := fractions.fraction(i64(36529), i64(12409100000))
+ f2 := fractions.fraction(i64(418754901), i64(9174901000))
+ sum := f1 + f2
+ assert sum.str() == '5196706591957729/113852263999100000'
+}
+
+fn test_4_by_8_plus_minus_5_by_10() {
+ f1 := fractions.fraction(4, 8)
+ f2 := fractions.fraction(-5, 10)
+ diff := f2 + f1
+ assert diff.f64() == 0
+ assert diff.str() == '0/1'
+}
+
+fn test_4_by_8_minus_5_by_10() {
+ f1 := fractions.fraction(4, 8)
+ f2 := fractions.fraction(5, 10)
+ diff := f2 - f1
+ assert diff.f64() == 0
+ assert diff.str() == '0/1'
+}
+
+fn test_5_by_5_minus_8_by_8() {
+ f1 := fractions.fraction(5, 5)
+ f2 := fractions.fraction(8, 8)
+ diff := f2 - f1
+ assert diff.f64() == 0
+ assert diff.str() == '0/1'
+}
+
+fn test_9_by_3_minus_1_by_3() {
+ f1 := fractions.fraction(9, 3)
+ f2 := fractions.fraction(1, 3)
+ diff := f1 - f2
+ assert diff.str() == '8/3'
+}
+
+fn test_3_by_7_minus_1_by_4() {
+ f1 := fractions.fraction(3, 7)
+ f2 := fractions.fraction(1, 4)
+ diff := f1 - f2
+ assert diff.str() == '5/28'
+}
+
+fn test_36529_by_12409100000_minus_418754901_by_9174901000() {
+ f1 := fractions.fraction(i64(36529), i64(12409100000))
+ f2 := fractions.fraction(i64(418754901), i64(9174901000))
+ sum := f1 - f2
+ assert sum.str() == '-5196036292040471/113852263999100000'
+}
+
+fn test_4_by_8_times_5_by_10() {
+ f1 := fractions.fraction(4, 8)
+ f2 := fractions.fraction(5, 10)
+ product := f1 * f2
+ assert product.f64() == 0.25
+ assert product.str() == '1/4'
+}
+
+fn test_5_by_5_times_8_by_8() {
+ f1 := fractions.fraction(5, 5)
+ f2 := fractions.fraction(8, 8)
+ product := f1 * f2
+ assert product.f64() == 1.0
+ assert product.str() == '1/1'
+}
+
+fn test_9_by_3_times_1_by_3() {
+ f1 := fractions.fraction(9, 3)
+ f2 := fractions.fraction(1, 3)
+ product := f1 * f2
+ assert product.f64() == 1.0
+ assert product.str() == '1/1'
+}
+
+fn test_3_by_7_times_1_by_4() {
+ f1 := fractions.fraction(3, 7)
+ f2 := fractions.fraction(1, 4)
+ product := f2 * f1
+ assert product.f64() == (3.0 / 28.0)
+ assert product.str() == '3/28'
+}
+
+fn test_4_by_8_over_5_by_10() {
+ f1 := fractions.fraction(4, 8)
+ f2 := fractions.fraction(5, 10)
+ q := f1 / f2
+ assert q.f64() == 1.0
+ assert q.str() == '1/1'
+}
+
+fn test_5_by_5_over_8_by_8() {
+ f1 := fractions.fraction(5, 5)
+ f2 := fractions.fraction(8, 8)
+ q := f1 / f2
+ assert q.f64() == 1.0
+ assert q.str() == '1/1'
+}
+
+fn test_9_by_3_over_1_by_3() {
+ f1 := fractions.fraction(9, 3)
+ f2 := fractions.fraction(1, 3)
+ q := f1 / f2
+ assert q.f64() == 9.0
+ assert q.str() == '9/1'
+}
+
+fn test_3_by_7_over_1_by_4() {
+ f1 := fractions.fraction(3, 7)
+ f2 := fractions.fraction(1, 4)
+ q := f1 / f2
+ assert q.str() == '12/7'
+}
+
+fn test_reciprocal_4_by_8() {
+ f := fractions.fraction(4, 8)
+ assert f.reciprocal().str() == '8/4'
+}
+
+fn test_reciprocal_5_by_10() {
+ f := fractions.fraction(5, 10)
+ assert f.reciprocal().str() == '10/5'
+}
+
+fn test_reciprocal_5_by_5() {
+ f := fractions.fraction(5, 5)
+ assert f.reciprocal().str() == '5/5'
+}
+
+fn test_reciprocal_8_by_8() {
+ f := fractions.fraction(8, 8)
+ assert f.reciprocal().str() == '8/8'
+}
+
+fn test_reciprocal_9_by_3() {
+ f := fractions.fraction(9, 3)
+ assert f.reciprocal().str() == '3/9'
+}
+
+fn test_reciprocal_1_by_3() {
+ f := fractions.fraction(1, 3)
+ assert f.reciprocal().str() == '3/1'
+}
+
+fn test_reciprocal_7_by_3() {
+ f := fractions.fraction(7, 3)
+ assert f.reciprocal().str() == '3/7'
+}
+
+fn test_reciprocal_1_by_4() {
+ f := fractions.fraction(1, 4)
+ assert f.reciprocal().str() == '4/1'
+}
+
+fn test_4_by_8_equals_5_by_10() {
+ f1 := fractions.fraction(4, 8)
+ f2 := fractions.fraction(5, 10)
+ assert f1.equals(f2)
+}
+
+fn test_1_by_2_does_not_equal_3_by_4() {
+ f1 := fractions.fraction(1, 2)
+ f2 := fractions.fraction(3, 4)
+ assert !f1.equals(f2)
+}
+
+fn test_reduce_3_by_9() {
+ f := fractions.fraction(3, 9)
+ assert f.reduce().equals(fractions.fraction(1, 3))
+}
+
+fn test_1_by_3_less_than_2_by_4() {
+ f1 := fractions.fraction(1, 3)
+ f2 := fractions.fraction(2, 4)
+ assert f1.lt(f2)
+ assert f1.le(f2)
+}
+
+fn test_2_by_3_greater_than_2_by_4() {
+ f1 := fractions.fraction(2, 3)
+ f2 := fractions.fraction(2, 4)
+ assert f1.gt(f2)
+ assert f1.ge(f2)
+}
+
+fn test_5_by_7_not_less_than_2_by_4() {
+ f1 := fractions.fraction(5, 7)
+ f2 := fractions.fraction(2, 4)
+ assert !f1.lt(f2)
+ assert !f1.le(f2)
+}
+
+fn test_49_by_75_not_greater_than_2_by_3() {
+ f1 := fractions.fraction(49, 75)
+ f2 := fractions.fraction(2, 3)
+ assert !f1.gt(f2)
+ assert !f1.ge(f2)
+}