#[cfg(feature = "exact")]
use core::cmp::Ordering;
use super::{Decimal, digit_count, power_of_ten};
use crate::{ErrorKind, Fraction};
impl<const DIGITS: u32, const MAX_EXPONENT: i32> Decimal<DIGITS, MAX_EXPONENT> {
pub fn to_fraction(self, max_denominator: u64) -> Option<Fraction> {
let negative = self.negative;
let coefficient = u128::from(self.coefficient);
if self.exponent >= 0 {
let scale = power_of_ten_checked(self.exponent.unsigned_abs())?;
let numerator = u64::try_from(coefficient.checked_mul(scale)?).ok()?;
return Fraction::new(negative, numerator, 1)
.ok()
.filter(|_| max_denominator >= 1);
}
let scale = power_of_ten_checked(self.exponent.unsigned_abs())?;
let bound = u128::from(max_denominator);
let (mut previous, mut current) = ((0_u128, 1_u128), (1_u128, 0_u128));
let (mut dividend, mut divisor) = (coefficient, scale);
while divisor != 0 {
let term = dividend / divisor;
let denominator = term.checked_mul(current.1)?.checked_add(previous.1)?;
if denominator > bound {
return None;
}
let numerator = term.checked_mul(current.0)?.checked_add(previous.0)?;
(previous, current) = (current, (numerator, denominator));
(dividend, divisor) = (divisor, dividend % divisor);
let difference = numerator
.checked_mul(scale)?
.abs_diff(coefficient.checked_mul(denominator)?);
if difference <= denominator {
return Fraction::new(
negative,
u64::try_from(numerator).ok()?,
u64::try_from(denominator).ok()?,
)
.ok();
}
}
None
}
}
fn power_of_ten_checked(exponent: u32) -> Option<u128> {
(exponent <= 38).then(|| power_of_ten(exponent))
}
impl<const DIGITS: u32, const MAX_EXPONENT: i32> Decimal<DIGITS, MAX_EXPONENT> {
fn from_ratio(negative: bool, numerator: u128, denominator: u128) -> Result<Self, ErrorKind> {
if denominator == 0 {
return Err(ErrorKind::DivisionByZero);
}
let mut coefficient = numerator / denominator;
let mut remainder = numerator % denominator;
let mut exponent: i64 = 0;
while remainder != 0 && digit_count(coefficient) < DIGITS + 2 {
let Some(scaled) = remainder.checked_mul(10) else {
break;
};
coefficient = coefficient * 10 + scaled / denominator;
remainder = scaled % denominator;
exponent -= 1;
}
Self::from_parts(negative, coefficient, exponent, remainder != 0)
}
}
impl<const DIGITS: u32, const MAX_EXPONENT: i32> TryFrom<Fraction>
for Decimal<DIGITS, MAX_EXPONENT>
{
type Error = ErrorKind;
fn try_from(value: Fraction) -> Result<Self, Self::Error> {
Self::from_ratio(
value.is_negative(),
u128::from(value.numerator()),
u128::from(value.denominator()),
)
}
}
impl<const DIGITS: u32, const MAX_EXPONENT: i32> TryFrom<Decimal<DIGITS, MAX_EXPONENT>>
for Fraction
{
type Error = ErrorKind;
fn try_from(value: Decimal<DIGITS, MAX_EXPONENT>) -> Result<Self, Self::Error> {
Self::from_decimal(
value.negative,
u128::from(value.coefficient),
i64::from(value.exponent),
)
}
}
#[cfg(feature = "exact")]
impl<const DIGITS: u32, const MAX_EXPONENT: i32> Decimal<DIGITS, MAX_EXPONENT> {
pub(crate) fn cmp_fraction(self, fraction: Fraction) -> Ordering {
let sign = |negative: bool, zero: bool| match (zero, negative) {
(true, _) => 0,
(false, true) => -1,
(false, false) => 1,
};
let sign_order = sign(self.negative, self.is_zero())
.cmp(&sign(fraction.is_negative(), fraction.is_zero()));
if sign_order != Ordering::Equal || self.is_zero() {
return sign_order;
}
let scaled = u128::from(self.coefficient) * u128::from(fraction.denominator());
let numerator = u128::from(fraction.numerator());
let shift = self.exponent.unsigned_abs();
let scale = (shift <= 38).then(|| power_of_ten(shift));
let magnitude = if self.exponent >= 0 {
scale
.and_then(|scale| scaled.checked_mul(scale))
.map_or(Ordering::Greater, |left| left.cmp(&numerator))
} else {
scale
.and_then(|scale| numerator.checked_mul(scale))
.map_or(Ordering::Less, |right| scaled.cmp(&right))
};
if self.negative {
magnitude.reverse()
} else {
magnitude
}
}
}