use std::fmt;
use ft_lib::ft_abs::ft_abs;
#[derive(Debug, PartialEq, Clone)]
struct Fraction {
numerator: i64,
denominator: i64,
}
impl fmt::Display for Fraction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.denominator == 1 {
write!(f, "{}", self.numerator)
} else {
write!(f, "{}/{}", self.numerator, self.denominator)
}
}
}
pub fn reduce_decimal(solution: f64) -> String {
let mut fraction: Fraction = decimal_to_fraction(solution);
reduce_fraction(&mut fraction);
if is_interesting_fraction(&fraction) {
fraction.to_string()
} else {
format!("{:.5}", solution)
}
}
fn decimal_to_fraction(decimal: f64) -> Fraction {
const MAX_DENOM: i64 = 1_000_000;
const EPSILON: f64 = 1e-7;
let negative: bool = decimal < 0.0;
let x: f64 = ft_abs(decimal);
let mut best_n: i64 = 0;
let mut best_d: i64 = 1;
let mut best_error: f64 = f64::MAX;
for d in 1..=MAX_DENOM {
let n: i64 = (x * d as f64).round() as i64;
let approx: f64 = n as f64 / d as f64;
let error: f64 = ft_abs(x - approx);
if error < best_error - EPSILON {
best_n = n;
best_d = d;
best_error = error;
if error < EPSILON {
break;
}
}
}
Fraction {
numerator: if negative { -best_n } else { best_n },
denominator: best_d,
}
}
fn reduce_fraction(fraction: &mut Fraction) {
let gcd_val = gcd(fraction.numerator.abs(), fraction.denominator);
fraction.numerator /= gcd_val;
fraction.denominator /= gcd_val;
}
fn gcd(mut a: i64, mut b: i64) -> i64 {
while b != 0 {
let temp = b;
b = a % b;
a = temp;
}
a
}
fn is_interesting_fraction(fraction: &Fraction) -> bool {
(fraction.denominator <= 10 && fraction.denominator >= -10)
&& (fraction.numerator <= 10 && fraction.numerator >= -10)
|| fraction.denominator == 1
|| (fraction.numerator % 2 == 0 && fraction.denominator % 2 == 0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_fractions() {
assert_eq!(
decimal_to_fraction(0.5),
Fraction {
numerator: 1,
denominator: 2
}
);
assert_eq!(
decimal_to_fraction(0.25),
Fraction {
numerator: 1,
denominator: 4
}
);
assert_eq!(
decimal_to_fraction(1.25),
Fraction {
numerator: 5,
denominator: 4
}
);
assert_eq!(
decimal_to_fraction(0.75),
Fraction {
numerator: 3,
denominator: 4
}
);
assert_eq!(
decimal_to_fraction(1.00),
Fraction {
numerator: 1,
denominator: 1
}
);
assert_eq!(
decimal_to_fraction(5.0),
Fraction {
numerator: 5,
denominator: 1
}
);
}
#[test]
fn test_repeating_fractions() {
assert_eq!(
decimal_to_fraction(0.3333333),
Fraction {
numerator: 1,
denominator: 3
}
);
assert_eq!(
decimal_to_fraction(0.6666667),
Fraction {
numerator: 2,
denominator: 3
}
);
assert_eq!(
decimal_to_fraction(0.2),
Fraction {
numerator: 1,
denominator: 5
}
);
}
#[test]
fn test_large_numbers() {
assert_eq!(
decimal_to_fraction(123.75),
Fraction {
numerator: 495,
denominator: 4
}
);
}
#[test]
fn test_negative_values() {
assert_eq!(
decimal_to_fraction(-0.5),
Fraction {
numerator: -1,
denominator: 2
}
);
assert_eq!(
decimal_to_fraction(-1.3333333),
Fraction {
numerator: -4,
denominator: 3
}
);
}
#[test]
fn test_rounding_edge_case() {
assert_eq!(
decimal_to_fraction(2.00000001),
Fraction {
numerator: 2,
denominator: 1
}
);
}
#[test]
fn test_zero() {
assert_eq!(
decimal_to_fraction(0.0),
Fraction {
numerator: 0,
denominator: 1
}
);
}
#[test]
fn test_one() {
assert_eq!(
decimal_to_fraction(1.0),
Fraction {
numerator: 1,
denominator: 1
}
);
}
#[test]
fn test_large_fraction() {
assert_eq!(
decimal_to_fraction(0.000123),
Fraction {
numerator: 1,
denominator: 8128
}
);
}
#[test]
fn test_close_to_whole_number() {
assert_eq!(
decimal_to_fraction(4.9999999),
Fraction {
numerator: 5,
denominator: 1
}
);
assert_eq!(
decimal_to_fraction(5.0000001),
Fraction {
numerator: 5,
denominator: 1
}
);
}
#[test]
fn test_half_plus_small() {
assert_eq!(
decimal_to_fraction(0.5000001),
Fraction {
numerator: 1,
denominator: 2
}
);
}
#[test]
fn test_negative_large_fraction() {
assert_eq!(
decimal_to_fraction(-12.75),
Fraction {
numerator: -51,
denominator: 4
}
);
}
#[test]
fn test_pi_approx() {
let frac = decimal_to_fraction(3.14159);
assert!((frac.numerator as f64 / frac.denominator as f64 - 3.14159).abs() < 1e-6);
}
#[test]
fn test_extreme_precision() {
assert_eq!(
decimal_to_fraction(0.0000001),
Fraction {
numerator: 0,
denominator: 1
}
);
}
#[test]
fn test_fraction_reduction() {
let mut frac = Fraction {
numerator: 2,
denominator: 4,
};
reduce_fraction(&mut frac);
assert_eq!(
frac,
Fraction {
numerator: 1,
denominator: 2
}
);
let mut frac = Fraction {
numerator: 100,
denominator: 400,
};
reduce_fraction(&mut frac);
assert_eq!(
frac,
Fraction {
numerator: 1,
denominator: 4
}
);
let mut frac = Fraction {
numerator: 10,
denominator: 10,
};
reduce_fraction(&mut frac);
assert_eq!(
frac,
Fraction {
numerator: 1,
denominator: 1
}
);
let mut frac = Fraction {
numerator: 10,
denominator: 2,
};
reduce_fraction(&mut frac);
assert_eq!(
frac,
Fraction {
numerator: 5,
denominator: 1
}
);
}
#[test]
fn test_is_interesting_fraction() {
assert_eq!(
is_interesting_fraction(&Fraction {
numerator: 1,
denominator: 3
}),
true
);
assert_eq!(
is_interesting_fraction(&Fraction {
numerator: 2,
denominator: 5
}),
true
);
assert_eq!(
is_interesting_fraction(&Fraction {
numerator: 5,
denominator: 1
}),
true
);
assert_eq!(
is_interesting_fraction(&Fraction {
numerator: 10,
denominator: 2
}),
true
);
assert_eq!(
is_interesting_fraction(&Fraction {
numerator: 7,
denominator: 6
}),
true
);
assert_eq!(
is_interesting_fraction(&Fraction {
numerator: 0,
denominator: 1
}),
true
);
assert_eq!(
is_interesting_fraction(&Fraction {
numerator: 495,
denominator: 4
}),
false
)
}
}