use crate::math::{Floor, One, Zero};
use core::ops::{Div, Mul, Rem, Sub};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Rational<N, D> {
pub numerator: N,
pub denominator: D,
}
impl<N, D> Rational<N, D> {
pub fn new(numerator: N, denominator: D) -> Self {
Self {
numerator,
denominator,
}
}
}
impl<N, D> From<N> for Rational<N, D>
where
D: One,
{
fn from(value: N) -> Self {
Self {
numerator: value,
denominator: D::one(),
}
}
}
pub trait ApplyRationalPrecision<T> {
fn apply_rational_precision(x: T) -> Self;
fn precision() -> Self;
}
#[cfg(feature = "std")]
impl ApplyRationalPrecision<f32> for u32 {
fn apply_rational_precision(x: f32) -> Self {
f32::round(1000000000. * x) as Self
}
fn precision() -> Self {
1000000000
}
}
#[cfg(feature = "libm")]
impl ApplyRationalPrecision<f32> for u32 {
fn apply_rational_precision(x: f32) -> Self {
libm::roundf(1000000000. * x) as Self
}
fn precision() -> Self {
1000000000
}
}
impl<T> Rational<T, T> {
pub fn from_float<F>(value: F) -> Self
where
T: ApplyRationalPrecision<F>
+ PartialOrd
+ Rem<Output = T>
+ Clone
+ Zero
+ Div<Output = T>,
F: Floor<Output = F> + Clone + Sub<Output = F> + Mul<Output = F>,
{
let frac = value.clone() - value.floor();
let frac_mul_precision: T = ApplyRationalPrecision::apply_rational_precision(frac);
let precision: T = ApplyRationalPrecision::precision();
let gcd = greatest_common_divisor(frac_mul_precision.clone(), precision.clone());
let denominator = precision / gcd.clone();
let numerator = frac_mul_precision / gcd;
Self {
numerator,
denominator,
}
}
}
fn greatest_common_divisor<T>(a: T, b: T) -> T
where
T: Zero + PartialOrd + Rem<Output = T> + Clone,
{
if a == T::zero() {
b
} else if b == T::zero() {
a
} else if a < b {
greatest_common_divisor(a.clone(), b % a)
} else {
greatest_common_divisor(b.clone(), a % b)
}
}