Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,28 @@
|
||||
# math.fractions
|
||||
|
||||
`math.fractions` provides exact rational arithmetic.
|
||||
|
||||
## Existing `i64` API
|
||||
|
||||
```v
|
||||
import math.fractions
|
||||
|
||||
f := fractions.fraction(4, 8)
|
||||
assert f.reduce().str() == '1/2'
|
||||
```
|
||||
|
||||
## Generic backends
|
||||
|
||||
`fractions.Rational[T]` supports signed builtin integers and `math.big.Integer`.
|
||||
|
||||
```v
|
||||
import math.big
|
||||
import math.fractions
|
||||
|
||||
huge := big.integer_from_string('1000000000000000000000000000000')!
|
||||
f := fractions.big_fraction(huge, big.integer_from_int(3))
|
||||
g := fractions.rational(i32(5), i32(10))
|
||||
|
||||
assert f.reduce().str() == '1000000000000000000000000000000/3'
|
||||
assert g.reduce().str() == '1/2'
|
||||
```
|
||||
@@ -0,0 +1,117 @@
|
||||
// Copyright (c) 2019-2024 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
|
||||
const max_iterations = 50
|
||||
const 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, default_eps)
|
||||
}
|
||||
|
||||
// approximate_with_eps returns a Fraction
|
||||
pub fn approximate_with_eps(val f64, eps f64) Fraction {
|
||||
if val == 0.0 {
|
||||
return zero
|
||||
}
|
||||
if eps < 0.0 {
|
||||
panic('Epsilon value cannot be negative.')
|
||||
}
|
||||
if math.abs(val) > 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 := 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 .. 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.abs(val - partial.f64()) < eps {
|
||||
return partial
|
||||
}
|
||||
frac -= f64(den)
|
||||
}
|
||||
panic("Couldn't converge. Please create an issue on https://github.com/vlang/v")
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
// Copyright (c) 2019-2024 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 == fractions.fraction(1, 2)
|
||||
}
|
||||
|
||||
fn test_third() {
|
||||
float_val := 1.0 / 3.0
|
||||
fract_val := fractions.approximate(float_val)
|
||||
assert fract_val == fractions.fraction(1, 3)
|
||||
}
|
||||
|
||||
fn test_minus_one_twelfth() {
|
||||
float_val := -1.0 / 12.0
|
||||
fract_val := fractions.approximate(float_val)
|
||||
assert fract_val == fractions.fraction(-1, 12)
|
||||
}
|
||||
|
||||
fn test_zero() {
|
||||
float_val := 0.0
|
||||
println('Pre')
|
||||
fract_val := fractions.approximate(float_val)
|
||||
println('Post')
|
||||
assert fract_val == fractions.fraction(0, 1)
|
||||
}
|
||||
|
||||
fn test_minus_one() {
|
||||
float_val := -1.0
|
||||
fract_val := fractions.approximate(float_val)
|
||||
assert fract_val == fractions.fraction(-1, 1)
|
||||
}
|
||||
|
||||
fn test_thirty_three() {
|
||||
float_val := 33.0
|
||||
fract_val := fractions.approximate(float_val)
|
||||
assert fract_val == fractions.fraction(33, 1)
|
||||
}
|
||||
|
||||
fn test_millionth() {
|
||||
float_val := 1.0 / 1000000.0
|
||||
fract_val := fractions.approximate(float_val)
|
||||
assert fract_val == 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 == fractions.fraction(-27, 57)
|
||||
}
|
||||
|
||||
fn test_29_by_104() {
|
||||
float_val := 29.0 / 104.0
|
||||
fract_val := fractions.approximate(float_val)
|
||||
assert fract_val == 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) == fractions.fraction(22, 7)
|
||||
}
|
||||
|
||||
fn test_pi_2_digits() {
|
||||
assert fractions.approximate_with_eps(math.pi, 5.0e-3) == fractions.fraction(22, 7)
|
||||
}
|
||||
|
||||
fn test_pi_3_digits() {
|
||||
assert fractions.approximate_with_eps(math.pi, 5.0e-4) == fractions.fraction(333, 106)
|
||||
}
|
||||
|
||||
fn test_pi_4_digits() {
|
||||
assert fractions.approximate_with_eps(math.pi, 5.0e-5) == fractions.fraction(355, 113)
|
||||
}
|
||||
|
||||
fn test_pi_5_digits() {
|
||||
assert fractions.approximate_with_eps(math.pi, 5.0e-6) == fractions.fraction(355, 113)
|
||||
}
|
||||
|
||||
fn test_pi_6_digits() {
|
||||
assert fractions.approximate_with_eps(math.pi, 5.0e-7) == fractions.fraction(355, 113)
|
||||
}
|
||||
|
||||
fn test_pi_7_digits() {
|
||||
assert fractions.approximate_with_eps(math.pi, 5.0e-8) == fractions.fraction(103993, 33102)
|
||||
}
|
||||
|
||||
fn test_pi_8_digits() {
|
||||
assert fractions.approximate_with_eps(math.pi, 5.0e-9) == fractions.fraction(103993, 33102)
|
||||
}
|
||||
|
||||
fn test_pi_9_digits() {
|
||||
assert fractions.approximate_with_eps(math.pi, 5.0e-10) == fractions.fraction(104348, 33215)
|
||||
}
|
||||
|
||||
fn test_pi_10_digits() {
|
||||
assert fractions.approximate_with_eps(math.pi, 5.0e-11) == fractions.fraction(312689, 99532)
|
||||
}
|
||||
|
||||
fn test_pi_11_digits() {
|
||||
assert fractions.approximate_with_eps(math.pi, 5.0e-12) == fractions.fraction(1146408, 364913)
|
||||
}
|
||||
|
||||
fn test_pi_12_digits() {
|
||||
assert fractions.approximate_with_eps(math.pi, 5.0e-13) == fractions.fraction(4272943, 1360120)
|
||||
}
|
||||
|
||||
fn test_phi_1_digit() {
|
||||
assert fractions.approximate_with_eps(math.phi, 5.0e-2) == fractions.fraction(5, 3)
|
||||
}
|
||||
|
||||
fn test_phi_2_digits() {
|
||||
assert fractions.approximate_with_eps(math.phi, 5.0e-3) == fractions.fraction(21, 13)
|
||||
}
|
||||
|
||||
fn test_phi_3_digits() {
|
||||
assert fractions.approximate_with_eps(math.phi, 5.0e-4) == fractions.fraction(55, 34)
|
||||
}
|
||||
|
||||
fn test_phi_4_digits() {
|
||||
assert fractions.approximate_with_eps(math.phi, 5.0e-5) == fractions.fraction(233, 144)
|
||||
}
|
||||
|
||||
fn test_phi_5_digits() {
|
||||
assert fractions.approximate_with_eps(math.phi, 5.0e-6) == fractions.fraction(610, 377)
|
||||
}
|
||||
|
||||
fn test_phi_6_digits() {
|
||||
assert fractions.approximate_with_eps(math.phi, 5.0e-7) == fractions.fraction(1597, 987)
|
||||
}
|
||||
|
||||
fn test_phi_7_digits() {
|
||||
assert fractions.approximate_with_eps(math.phi, 5.0e-8) == fractions.fraction(6765, 4181)
|
||||
}
|
||||
|
||||
fn test_phi_8_digits() {
|
||||
assert fractions.approximate_with_eps(math.phi, 5.0e-9) == fractions.fraction(17711, 10946)
|
||||
}
|
||||
|
||||
fn test_phi_9_digits() {
|
||||
assert fractions.approximate_with_eps(math.phi, 5.0e-10) == fractions.fraction(75025, 46368)
|
||||
}
|
||||
|
||||
fn test_phi_10_digits() {
|
||||
assert fractions.approximate_with_eps(math.phi, 5.0e-11) == fractions.fraction(196418, 121393)
|
||||
}
|
||||
|
||||
fn test_phi_11_digits() {
|
||||
assert fractions.approximate_with_eps(math.phi, 5.0e-12) == fractions.fraction(514229, 317811)
|
||||
}
|
||||
|
||||
fn test_phi_12_digits() {
|
||||
assert fractions.approximate_with_eps(math.phi, 5.0e-13) == fractions.fraction(2178309, 1346269)
|
||||
}
|
||||
@@ -0,0 +1,421 @@
|
||||
// Copyright (c) 2019-2024 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.big
|
||||
|
||||
// Fraction stores a numerator and denominator using the existing `i64` backend.
|
||||
pub struct Fraction {
|
||||
pub:
|
||||
n i64
|
||||
d i64
|
||||
is_reduced bool
|
||||
}
|
||||
|
||||
// Rational stores a numerator and denominator using the backend type `T`.
|
||||
//
|
||||
// Supported backend types are signed builtin integers and `math.big.Integer`.
|
||||
pub struct Rational[T] {
|
||||
pub:
|
||||
n T
|
||||
d T
|
||||
is_reduced bool
|
||||
}
|
||||
|
||||
// fraction creates an `i64`-backed Fraction.
|
||||
//
|
||||
// The denominator must be non-zero. Negative denominators are normalized so
|
||||
// that the stored denominator is always positive.
|
||||
pub fn fraction(n i64, d i64) Fraction {
|
||||
return rational_to_fraction(rational[i64](n, d))
|
||||
}
|
||||
|
||||
// big_fraction creates a `math.big.Integer`-backed Rational.
|
||||
//
|
||||
// The denominator must be non-zero. Negative denominators are normalized so
|
||||
// that the stored denominator is always positive.
|
||||
pub fn big_fraction(n big.Integer, d big.Integer) Rational[big.Integer] {
|
||||
return rational[big.Integer](n, d)
|
||||
}
|
||||
|
||||
// rational creates a Rational backed by the concrete numeric type `T`.
|
||||
//
|
||||
// Fractions created this way are not reduced automatically, but `is_reduced`
|
||||
// reflects whether the input was already in lowest terms.
|
||||
pub fn rational[T](n T, d T) Rational[T] {
|
||||
ensure_supported_backend[T]()
|
||||
if is_zero_value[T](d) {
|
||||
panic('Denominator cannot be zero')
|
||||
}
|
||||
if is_negative_value[T](d) {
|
||||
return rational[T](negate_value[T](n), negate_value[T](d))
|
||||
}
|
||||
return Rational[T]{
|
||||
n: n
|
||||
d: d
|
||||
is_reduced: gcd_values[T](n, d) == one_value[T]()
|
||||
}
|
||||
}
|
||||
|
||||
// str returns the fraction in `n/d` form.
|
||||
pub fn (f Fraction) str() string {
|
||||
return f.to_rational().str()
|
||||
}
|
||||
|
||||
// Fraction add using operator overloading.
|
||||
pub fn (f1 Fraction) + (f2 Fraction) Fraction {
|
||||
return rational_to_fraction(f1.to_rational() + f2.to_rational())
|
||||
}
|
||||
|
||||
// Fraction subtract using operator overloading.
|
||||
pub fn (f1 Fraction) - (f2 Fraction) Fraction {
|
||||
return rational_to_fraction(f1.to_rational() - f2.to_rational())
|
||||
}
|
||||
|
||||
// Fraction multiply using operator overloading.
|
||||
pub fn (f1 Fraction) * (f2 Fraction) Fraction {
|
||||
return rational_to_fraction(f1.to_rational() * f2.to_rational())
|
||||
}
|
||||
|
||||
// Fraction divide using operator overloading.
|
||||
pub fn (f1 Fraction) / (f2 Fraction) Fraction {
|
||||
return rational_to_fraction(f1.to_rational() / f2.to_rational())
|
||||
}
|
||||
|
||||
// negate returns the additive inverse of the Fraction.
|
||||
pub fn (f Fraction) negate() Fraction {
|
||||
return rational_to_fraction(f.to_rational().negate())
|
||||
}
|
||||
|
||||
// reciprocal returns the reciprocal of the Fraction.
|
||||
pub fn (f Fraction) reciprocal() Fraction {
|
||||
return rational_to_fraction(f.to_rational().reciprocal())
|
||||
}
|
||||
|
||||
// reduce returns the Fraction reduced to lowest terms.
|
||||
pub fn (f Fraction) reduce() Fraction {
|
||||
return rational_to_fraction(f.to_rational().reduce())
|
||||
}
|
||||
|
||||
// f64 converts the Fraction to 64-bit floating point.
|
||||
pub fn (f Fraction) f64() f64 {
|
||||
return f.to_rational().f64()
|
||||
}
|
||||
|
||||
// return true if f1 == f2.
|
||||
pub fn (f1 Fraction) == (f2 Fraction) bool {
|
||||
return cmp_fraction(f1, f2) == 0
|
||||
}
|
||||
|
||||
// return true if f1 < f2.
|
||||
pub fn (f1 Fraction) < (f2 Fraction) bool {
|
||||
return cmp_fraction(f1, f2) < 0
|
||||
}
|
||||
|
||||
// str returns the fraction in `n/d` form.
|
||||
pub fn (f Rational[T]) 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[T](f1 Rational[T], f2 Rational[T], addition bool) Rational[T] {
|
||||
d1 := gcd_values[T](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 == one_value[T]() {
|
||||
num1n2d := f1.n * f2.d
|
||||
num1d2n := f1.d * f2.n
|
||||
if addition {
|
||||
return Rational[T]{
|
||||
n: add_values[T](num1n2d, num1d2n)
|
||||
d: f1.d * f2.d
|
||||
is_reduced: true
|
||||
}
|
||||
}
|
||||
return Rational[T]{
|
||||
n: sub_values[T](num1n2d, num1d2n)
|
||||
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
|
||||
if addition {
|
||||
t := add_values[T](term1, term2)
|
||||
d2 := gcd_values[T](t, d1)
|
||||
return Rational[T]{
|
||||
n: t / d2
|
||||
d: f1den * (f2.d / d2)
|
||||
is_reduced: true
|
||||
}
|
||||
}
|
||||
t := sub_values[T](term1, term2)
|
||||
d2 := gcd_values[T](t, d1)
|
||||
return Rational[T]{
|
||||
n: t / d2
|
||||
d: f1den * (f2.d / d2)
|
||||
is_reduced: true
|
||||
}
|
||||
}
|
||||
|
||||
// Fraction add using operator overloading.
|
||||
pub fn (f1 Rational[T]) + (f2 Rational[T]) Rational[T] {
|
||||
return general_addition_result[T](f1.reduce(), f2.reduce(), true)
|
||||
}
|
||||
|
||||
// Fraction subtract using operator overloading.
|
||||
pub fn (f1 Rational[T]) - (f2 Rational[T]) Rational[T] {
|
||||
return general_addition_result[T](f1.reduce(), f2.reduce(), false)
|
||||
}
|
||||
|
||||
// Returns a correctly reduced result for both multiplication and division.
|
||||
// NOTE: requires reduced inputs.
|
||||
fn general_multiplication_result[T](f1 Rational[T], f2 Rational[T], multiplication bool) Rational[T] {
|
||||
// * 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 := gcd_values[T](f1.n, f2.d)
|
||||
d2 := gcd_values[T](f1.d, f2.n)
|
||||
return Rational[T]{
|
||||
n: (f1.n / d1) * (f2.n / d2)
|
||||
d: (f2.d / d1) * (f1.d / d2)
|
||||
is_reduced: true
|
||||
}
|
||||
} else {
|
||||
d1 := gcd_values[T](f1.n, f2.n)
|
||||
d2 := gcd_values[T](f1.d, f2.d)
|
||||
return Rational[T]{
|
||||
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 Rational[T]) * (f2 Rational[T]) Rational[T] {
|
||||
return general_multiplication_result[T](f1.reduce(), f2.reduce(), true)
|
||||
}
|
||||
|
||||
// Fraction divide using operator overloading.
|
||||
pub fn (f1 Rational[T]) / (f2 Rational[T]) Rational[T] {
|
||||
if is_zero_value[T](f2.n) {
|
||||
panic('Cannot divide by zero')
|
||||
}
|
||||
// If the second fraction is negative, it will mess up the sign.
|
||||
// We need a positive denominator.
|
||||
if is_negative_value[T](f2.n) {
|
||||
return f1.negate() / f2.negate()
|
||||
}
|
||||
return general_multiplication_result[T](f1.reduce(), f2.reduce(), false)
|
||||
}
|
||||
|
||||
// negate returns the additive inverse of the Rational.
|
||||
pub fn (f Rational[T]) negate() Rational[T] {
|
||||
return Rational[T]{
|
||||
n: negate_value[T](f.n)
|
||||
d: f.d
|
||||
is_reduced: f.is_reduced
|
||||
}
|
||||
}
|
||||
|
||||
// reciprocal returns the reciprocal of the Rational.
|
||||
pub fn (f Rational[T]) reciprocal() Rational[T] {
|
||||
if is_zero_value[T](f.n) {
|
||||
panic('Denominator cannot be zero')
|
||||
}
|
||||
return rational[T](f.d, f.n)
|
||||
}
|
||||
|
||||
// reduce returns the Rational reduced to lowest terms.
|
||||
pub fn (f Rational[T]) reduce() Rational[T] {
|
||||
if f.is_reduced {
|
||||
return f
|
||||
}
|
||||
cf := gcd_values[T](f.n, f.d)
|
||||
return Rational[T]{
|
||||
n: f.n / cf
|
||||
d: f.d / cf
|
||||
is_reduced: true
|
||||
}
|
||||
}
|
||||
|
||||
// f64 converts the Rational to 64-bit floating point.
|
||||
pub fn (f Rational[T]) f64() f64 {
|
||||
return to_f64_value[T](f.n) / to_f64_value[T](f.d)
|
||||
}
|
||||
|
||||
//
|
||||
// + ------------------+
|
||||
// | Utility functions.|
|
||||
// + ------------------+
|
||||
//
|
||||
|
||||
fn ensure_supported_backend[T]() {
|
||||
$if T is big.Integer || T is i8 || T is i16 || T is i32 || T is i64 || T is int {
|
||||
} $else {
|
||||
$compile_error('fractions: Rational only supports signed builtin integers and math.big.Integer backends')
|
||||
}
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn (f Fraction) to_rational() Rational[i64] {
|
||||
return Rational[i64]{
|
||||
n: f.n
|
||||
d: f.d
|
||||
is_reduced: f.is_reduced
|
||||
}
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn rational_to_fraction(r Rational[i64]) Fraction {
|
||||
return Fraction{
|
||||
n: r.n
|
||||
d: r.d
|
||||
is_reduced: r.is_reduced
|
||||
}
|
||||
}
|
||||
|
||||
fn cmp_fraction(f1 Fraction, f2 Fraction) int {
|
||||
return cmp_values[big.Integer](to_big_integer[i64](f1.n) * to_big_integer[i64](f2.d),
|
||||
to_big_integer[i64](f2.n) * to_big_integer[i64](f1.d))
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn zero_value[T]() T {
|
||||
$if T is big.Integer {
|
||||
return big.zero_int
|
||||
} $else {
|
||||
return T(0)
|
||||
}
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn one_value[T]() T {
|
||||
$if T is big.Integer {
|
||||
return big.one_int
|
||||
} $else {
|
||||
return T(1)
|
||||
}
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn is_zero_value[T](value T) bool {
|
||||
return value == zero_value[T]()
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn is_negative_value[T](value T) bool {
|
||||
$if T is big.Integer {
|
||||
return value < big.zero_int
|
||||
} $else {
|
||||
return value < 0
|
||||
}
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn negate_value[T](value T) T {
|
||||
$if T is big.Integer {
|
||||
return value.neg()
|
||||
} $else {
|
||||
return -value
|
||||
}
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn abs_value[T](value T) T {
|
||||
$if T is big.Integer {
|
||||
return value.abs()
|
||||
} $else {
|
||||
return if value < 0 { -value } else { value }
|
||||
}
|
||||
}
|
||||
|
||||
fn gcd_values[T](a T, b T) T {
|
||||
$if T is big.Integer {
|
||||
return a.gcd(b)
|
||||
} $else {
|
||||
mut x := abs_value[T](a)
|
||||
mut y := abs_value[T](b)
|
||||
for y != zero_value[T]() {
|
||||
x %= y
|
||||
if x == zero_value[T]() {
|
||||
return y
|
||||
}
|
||||
y %= x
|
||||
}
|
||||
return x
|
||||
}
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn to_f64_value[T](value T) f64 {
|
||||
$if T is big.Integer {
|
||||
return value.str().f64()
|
||||
} $else {
|
||||
return f64(value)
|
||||
}
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn to_big_integer[T](value T) big.Integer {
|
||||
$if T is big.Integer {
|
||||
return value
|
||||
} $else {
|
||||
return big.integer_from_i64(i64(value))
|
||||
}
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn add_values[T](a T, b T) T {
|
||||
return a + b
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn sub_values[T](a T, b T) T {
|
||||
return a - b
|
||||
}
|
||||
|
||||
// cmp compares the two arguments, returns 0 when equal, 1 when the first is bigger, -1 otherwise.
|
||||
fn cmp[T](f1 Rational[T], f2 Rational[T]) int {
|
||||
$if T is big.Integer {
|
||||
return cmp_values[T](f1.n * f2.d, f2.n * f1.d)
|
||||
} $else {
|
||||
return cmp_values[big.Integer](to_big_integer[T](f1.n) * to_big_integer[T](f2.d),
|
||||
to_big_integer[T](f2.n) * to_big_integer[T](f1.d))
|
||||
}
|
||||
}
|
||||
|
||||
fn cmp_values[T](a T, b T) int {
|
||||
if a == b {
|
||||
return 0
|
||||
}
|
||||
return if a > b { 1 } else { -1 }
|
||||
}
|
||||
|
||||
// return true if f1 == f2.
|
||||
pub fn (f1 Rational[T]) == (f2 Rational[T]) bool {
|
||||
return cmp[T](f1, f2) == 0
|
||||
}
|
||||
|
||||
// return true if f1 < f2.
|
||||
pub fn (f1 Rational[T]) < (f2 Rational[T]) bool {
|
||||
return cmp[T](f1, f2) < 0
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
// Copyright (c) 2019-2024 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.big
|
||||
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 bi(s string) big.Integer {
|
||||
return big.integer_from_string(s) or { panic(err) }
|
||||
}
|
||||
|
||||
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 == 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 == 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 == 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 == 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 == f2
|
||||
}
|
||||
|
||||
fn test_1_by_2_does_not_equal_3_by_4() {
|
||||
f1 := fractions.fraction(1, 2)
|
||||
f2 := fractions.fraction(3, 4)
|
||||
assert f1 != f2
|
||||
}
|
||||
|
||||
fn test_reduce_3_by_9() {
|
||||
f := fractions.fraction(3, 9)
|
||||
assert f.reduce() == 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 < f2
|
||||
assert f1 <= f2
|
||||
}
|
||||
|
||||
fn test_2_by_3_greater_than_2_by_4() {
|
||||
f1 := fractions.fraction(2, 3)
|
||||
f2 := fractions.fraction(2, 4)
|
||||
assert f1 > f2
|
||||
assert f1 >= f2
|
||||
}
|
||||
|
||||
fn test_5_by_7_not_less_than_2_by_4() {
|
||||
f1 := fractions.fraction(5, 7)
|
||||
f2 := fractions.fraction(2, 4)
|
||||
assert !(f1 < f2)
|
||||
assert !(f1 <= f2)
|
||||
}
|
||||
|
||||
fn test_49_by_75_not_greater_than_2_by_3() {
|
||||
f1 := fractions.fraction(49, 75)
|
||||
f2 := fractions.fraction(2, 3)
|
||||
assert !(f1 > f2)
|
||||
assert !(f1 >= f2)
|
||||
}
|
||||
|
||||
fn test_i32_rational_addition() {
|
||||
f1 := fractions.rational(i32(4), i32(8))
|
||||
f2 := fractions.rational(i32(5), i32(10))
|
||||
sum := f1 + f2
|
||||
assert sum.str() == '1/1'
|
||||
assert sum == fractions.rational(i32(1), i32(1))
|
||||
}
|
||||
|
||||
fn test_big_fraction_normalizes_and_reduces() {
|
||||
f := fractions.big_fraction(bi('-14'), bi('-21'))
|
||||
assert f.str() == '14/21'
|
||||
assert f.reciprocal().str() == '21/14'
|
||||
assert f.reduce().str() == '2/3'
|
||||
}
|
||||
|
||||
fn test_big_fraction_addition() {
|
||||
f1 := fractions.big_fraction(bi('100000000000000000000000000000000000000'), bi('3'))
|
||||
f2 := fractions.big_fraction(bi('5'), bi('6'))
|
||||
sum := f1 + f2
|
||||
assert sum.str() == '200000000000000000000000000000000000005/6'
|
||||
assert sum > f1
|
||||
}
|
||||
|
||||
fn test_big_fraction_exact_comparison_beyond_i64() {
|
||||
huge := bi('9223372036854775808123456789')
|
||||
f1 := fractions.big_fraction(huge + big.one_int, huge)
|
||||
f2 := fractions.big_fraction(huge, huge + big.one_int)
|
||||
assert f1 > f2
|
||||
assert f1 != f2
|
||||
}
|
||||
Reference in new issue
Block a user