use crate::{
rbig::{RBig, Relaxed},
repr::Repr,
};
use dashu_base::{Approximation, ConversionError, DivRem, Gcd};
use dashu_float::{
round::{ErrorBounds, Round, Rounded},
Context, FBig, Repr as FBigRepr,
};
use dashu_int::{IBig, UBig, Word};
impl<R: Round, const B: Word> TryFrom<Repr> for FBig<R, B> {
type Error = ConversionError;
fn try_from(v: Repr) -> Result<Self, Self::Error> {
let Repr {
numerator,
denominator,
} = v;
let (sign, mut num_mag) = numerator.into_parts();
let g = (&num_mag).gcd(&denominator);
num_mag /= &g;
let den = denominator / &g;
let base = UBig::from_word(B);
let mut k = 0usize;
let mut d = den.clone();
loop {
let g = (&d).gcd(&base);
if g.is_one() {
break;
}
d /= &g;
k += 1;
}
if !d.is_one() {
return Err(ConversionError::LossOfPrecision);
}
let signif_mag = if k == 0 {
num_mag
} else {
(num_mag * base.pow(k)) / &den
};
let signif = IBig::from_parts(sign, signif_mag);
let repr = FBigRepr::new(signif, -(k as isize));
let precision = repr.digits().max(1);
Ok(FBig::from_repr(repr, Context::new(precision)))
}
}
impl<const B: Word> TryFrom<FBigRepr<B>> for Repr {
type Error = ConversionError;
fn try_from(value: FBigRepr<B>) -> Result<Self, Self::Error> {
if value.is_infinite() {
Err(ConversionError::OutOfBounds)
} else {
let (signif, exp) = value.into_parts();
let (numerator, denominator) = if exp >= 0 {
(signif * UBig::from_word(B).pow(exp as usize), UBig::ONE)
} else {
(signif, UBig::from_word(B).pow((-exp) as usize))
};
Ok(Repr {
numerator,
denominator,
})
}
}
}
impl<R: Round, const B: Word> TryFrom<FBig<R, B>> for Repr {
type Error = ConversionError;
#[inline]
fn try_from(value: FBig<R, B>) -> Result<Self, Self::Error> {
value.into_repr().try_into()
}
}
macro_rules! forward_conversion_to_repr {
($t:ident, $reduce:ident) => {
impl<R: Round, const B: Word> TryFrom<$t> for FBig<R, B> {
type Error = ConversionError;
#[inline]
fn try_from(v: $t) -> Result<Self, Self::Error> {
v.0.try_into()
}
}
impl<const B: Word> TryFrom<FBigRepr<B>> for $t {
type Error = ConversionError;
#[inline]
fn try_from(value: FBigRepr<B>) -> Result<Self, Self::Error> {
Repr::try_from(value).map(|repr| $t(repr.$reduce()))
}
}
impl<R: Round, const B: Word> TryFrom<FBig<R, B>> for $t {
type Error = ConversionError;
#[inline]
fn try_from(value: FBig<R, B>) -> Result<Self, Self::Error> {
Repr::try_from(value).map(|repr| $t(repr.$reduce()))
}
}
};
}
forward_conversion_to_repr!(RBig, reduce);
forward_conversion_to_repr!(Relaxed, reduce2);
impl Repr {
fn to_float<R: Round, const B: Word>(&self, precision: usize) -> Rounded<FBig<R, B>> {
assert!(precision > 0);
if self.numerator.is_zero() {
return FBig::ZERO.with_precision(precision);
}
let base = UBig::from_word(B);
let num_digits = self.numerator.ilog(&base);
let den_digits = self.denominator.ilog(&base);
let shift;
let (q, r) = if num_digits >= precision + den_digits {
shift = 0;
(&self.numerator).div_rem(&self.denominator)
} else {
shift = (precision + den_digits) - num_digits;
if B == 2 {
(&self.numerator << shift).div_rem(&self.denominator)
} else {
(&self.numerator * base.pow(shift)).div_rem(&self.denominator)
}
};
let rounded = if r.is_zero() {
Approximation::Exact(q)
} else {
let adjust = R::round_ratio(&q, r, self.denominator.as_ibig());
Approximation::Inexact(q + adjust, adjust)
};
let context = Context::<R>::new(precision);
rounded
.and_then(|n| context.convert_int(n))
.map(|f| f >> (shift as isize))
}
}
impl RBig {
#[inline]
pub fn to_float<R: Round, const B: Word>(&self, precision: usize) -> Rounded<FBig<R, B>> {
self.0.to_float(precision)
}
pub fn simplest_from_float<R: ErrorBounds, const B: Word>(f: &FBig<R, B>) -> Option<Self> {
if f.repr().is_infinite() {
return None;
} else if f.repr().is_pos_zero() {
return Some(Self::ZERO);
}
let (l, r, incl_l, incl_r) = R::error_bounds(f);
let lb = f - l.with_precision(f.precision() + 1).unwrap();
let rb = f + r.with_precision(f.precision() + 1).unwrap();
let left = Self::try_from(lb).unwrap();
let right = Self::try_from(rb).unwrap();
let mut simplest = Self::simplest_in(left.clone(), right.clone());
if incl_l && left.is_simpler_than(&simplest) {
simplest = left;
}
if incl_r && right.is_simpler_than(&simplest) {
simplest = right;
}
Some(simplest)
}
}
impl Relaxed {
#[inline]
pub fn to_float<R: Round, const B: Word>(&self, precision: usize) -> Rounded<FBig<R, B>> {
self.0.to_float(precision)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::RBig;
use core::str::FromStr;
use dashu_float::{DBig, FBig};
#[test]
fn test_rational_to_float_exact() {
let cases: &[(RBig, &str)] = &[
(RBig::from_parts(1.into(), 2u8.into()), "0.5"),
(RBig::from_parts(1.into(), 4u8.into()), "0.25"),
(RBig::from_parts(3.into(), 4u8.into()), "0.75"),
(RBig::from_parts(1.into(), 8u8.into()), "0.125"),
(RBig::from(100), "100"),
];
for (r, expected) in cases {
let f: DBig = r.clone().try_into().unwrap();
assert_eq!(f, DBig::from_str(expected).unwrap(), "exact convert {r:?}");
}
let f: FBig = RBig::from_parts(1.into(), 8u8.into()).try_into().unwrap();
assert_eq!(f, "0.001".parse::<FBig>().unwrap());
}
#[test]
fn test_rational_to_float_inexact() {
let r = RBig::from_parts(1.into(), 3u8.into());
let res: Result<DBig, _> = r.try_into();
assert!(matches!(res, Err(ConversionError::LossOfPrecision)));
let r = RBig::from_parts(7.into(), 12u8.into());
let res: Result<DBig, _> = r.try_into();
assert!(matches!(res, Err(ConversionError::LossOfPrecision)));
let r = RBig::from_parts(1.into(), 10u8.into());
let res: Result<FBig, _> = r.try_into();
assert!(matches!(res, Err(ConversionError::LossOfPrecision)));
}
}