use core::{cmp::min, mem};
use fpdec_core::ten_pow;
use crate::Decimal;
pub trait AsIntegerRatio: Copy + Sized {
fn as_integer_ratio(self) -> (i128, i128) {
(self.numerator(), self.denominator())
}
fn numerator(self) -> i128;
fn denominator(self) -> i128;
}
impl<T> AsIntegerRatio for T
where
T: Copy + Sized,
i128: From<T>,
{
#[inline(always)]
fn numerator(self) -> i128 {
i128::from(self)
}
#[inline(always)]
fn denominator(self) -> i128 {
1_i128
}
}
#[cfg(test)]
mod test_int_as_ratio {
use super::*;
macro_rules! gen_test_as_ratio {
($t:ty) => {
let i = <$t>::MAX;
assert_eq!(i.numerator(), i128::from(i));
assert_eq!(i.denominator(), 1_i128);
};
}
#[test]
fn test_as_ratio() {
gen_test_as_ratio!(u8);
gen_test_as_ratio!(i8);
gen_test_as_ratio!(u16);
gen_test_as_ratio!(i16);
gen_test_as_ratio!(u32);
gen_test_as_ratio!(i32);
gen_test_as_ratio!(u64);
gen_test_as_ratio!(i64);
gen_test_as_ratio!(i128);
}
}
#[inline]
fn gcd_special(numer: i128, denom_exp: u32) -> i128 {
assert_ne!(numer, 0);
assert!(denom_exp <= 38);
let mut u = numer.abs();
let utz = u.trailing_zeros();
u >>= utz;
#[allow(clippy::cast_possible_truncation)]
let mut v = ten_pow(denom_exp as u8) >> denom_exp;
while v != 0 {
v >>= v.trailing_zeros();
if u > v {
mem::swap(&mut u, &mut v);
}
v -= u;
}
u << min(utz, denom_exp)
}
impl AsIntegerRatio for Decimal {
#[allow(clippy::integer_division)]
fn as_integer_ratio(self) -> (i128, i128) {
if self.n_frac_digits == 0 || self.coeff == 0 {
return (self.coeff, 1);
}
let gcd = gcd_special(self.coeff, self.n_frac_digits as u32);
(self.coeff / gcd, self.denominator())
}
#[allow(clippy::integer_division)]
fn numerator(self) -> i128 {
if self.n_frac_digits == 0 || self.coeff == 0 {
return self.coeff;
}
let gcd = gcd_special(self.coeff, self.n_frac_digits as u32);
self.coeff / gcd
}
#[allow(clippy::integer_division)]
fn denominator(self) -> i128 {
if self.n_frac_digits == 0 || self.coeff == 0 {
return 1;
}
let gcd = gcd_special(self.coeff, self.n_frac_digits as u32);
ten_pow(self.n_frac_digits) / gcd
}
}
#[cfg(test)]
mod test_decimal_as_ratio {
use fpdec_core::MAX_N_FRAC_DIGITS;
use super::*;
#[test]
fn test_decimal_as_ratio() {
let d = Decimal::new_raw(0, MAX_N_FRAC_DIGITS);
assert_eq!(d.as_integer_ratio(), (0, 1));
let d = Decimal::new_raw(12345, 0);
assert_eq!(d.as_integer_ratio(), (12345, 1));
let d = Decimal::new_raw(12345, 4);
assert_eq!(d.as_integer_ratio(), (12345 / 5, ten_pow(4) / 5));
let d = Decimal::new_raw(123456, 4);
assert_eq!(d.as_integer_ratio(), (123456 / 16, ten_pow(4) / 16));
let d = Decimal::new_raw(1234567, 4);
assert_eq!(d.as_integer_ratio(), (1234567, ten_pow(4)));
let d = Decimal::new_raw(12345678, 9);
assert_eq!(d.as_integer_ratio(), (12345678 / 2, ten_pow(9) / 2));
}
#[test]
fn test_decimal_numerator() {
let d = Decimal::new_raw(0, MAX_N_FRAC_DIGITS);
assert_eq!(d.numerator(), 0);
let d = Decimal::new_raw(12345, 0);
assert_eq!(d.numerator(), 12345);
let d = Decimal::new_raw(12345, 4);
assert_eq!(d.numerator(), 12345 / 5);
let d = Decimal::new_raw(123456, 4);
assert_eq!(d.numerator(), 123456 / 16);
let d = Decimal::new_raw(1234567, 4);
assert_eq!(d.numerator(), 1234567);
let d = Decimal::new_raw(12345678, 9);
assert_eq!(d.numerator(), 12345678 / 2);
}
#[test]
fn test_decimal_denominator() {
let d = Decimal::new_raw(0, MAX_N_FRAC_DIGITS);
assert_eq!(d.denominator(), 1);
let d = Decimal::new_raw(12345, 0);
assert_eq!(d.denominator(), 1);
let d = Decimal::new_raw(12345, 4);
assert_eq!(d.denominator(), ten_pow(4) / 5);
let d = Decimal::new_raw(123456, 4);
assert_eq!(d.denominator(), ten_pow(4) / 16);
let d = Decimal::new_raw(1234567, 4);
assert_eq!(d.denominator(), ten_pow(4));
let d = Decimal::new_raw(12345678, 9);
assert_eq!(d.denominator(), ten_pow(9) / 2);
}
}