Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

+28
View File
@@ -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)
}
+421
View File
@@ -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
}
+305
View File
@@ -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
}