dashu_ratio/third_party/
dashu_float.rs

1use crate::{
2    rbig::{RBig, Relaxed},
3    repr::Repr,
4};
5use dashu_base::{Approximation, ConversionError, DivRem, Gcd};
6use dashu_float::{
7    round::{ErrorBounds, Round, Rounded},
8    Context, FBig, Repr as FBigRepr,
9};
10use dashu_int::{UBig, Word};
11
12// TODO(v0.5): make this fallible, only succeed when the conversion is exact.
13impl<R: Round, const B: Word> From<Repr> for FBig<R, B> {
14    #[inline]
15    fn from(v: Repr) -> Self {
16        let Repr {
17            numerator,
18            denominator,
19        } = v;
20        FBig::from(numerator) / FBig::from(denominator)
21    }
22}
23
24// TODO(v0.5): substitute this function
25#[allow(dead_code)]
26fn fbig_try_from_rbig<R: Round, const B: Word>(v: Repr) -> Result<FBig<R, B>, ConversionError> {
27    let Repr {
28        numerator,
29        denominator,
30    } = v;
31
32    let float_den = FBig::from(denominator);
33    let float_num = FBig::from(numerator);
34
35    // TODO: specialize for RBig (gcd is unnecessary)
36    if !float_num
37        .repr()
38        .significand()
39        .gcd(float_den.repr().significand())
40        .is_one()
41    {
42        return Err(ConversionError::LossOfPrecision);
43    }
44
45    Ok(float_den / float_num)
46}
47
48impl<const B: Word> TryFrom<FBigRepr<B>> for Repr {
49    type Error = ConversionError;
50    fn try_from(value: FBigRepr<B>) -> Result<Self, Self::Error> {
51        if value.is_infinite() {
52            Err(ConversionError::OutOfBounds)
53        } else {
54            let (signif, exp) = value.into_parts();
55            let (numerator, denominator) = if exp >= 0 {
56                (signif * UBig::from_word(B).pow(exp as usize), UBig::ONE)
57            } else {
58                (signif, UBig::from_word(B).pow((-exp) as usize))
59            };
60            Ok(Repr {
61                numerator,
62                denominator,
63            })
64        }
65    }
66}
67
68impl<R: Round, const B: Word> TryFrom<FBig<R, B>> for Repr {
69    type Error = ConversionError;
70    #[inline]
71    fn try_from(value: FBig<R, B>) -> Result<Self, Self::Error> {
72        value.into_repr().try_into()
73    }
74}
75
76macro_rules! forward_conversion_to_repr {
77    ($t:ident, $reduce:ident) => {
78        impl<R: Round, const B: Word> From<$t> for FBig<R, B> {
79            #[inline]
80            fn from(v: $t) -> Self {
81                v.0.into()
82            }
83        }
84
85        impl<const B: Word> TryFrom<FBigRepr<B>> for $t {
86            type Error = ConversionError;
87            #[inline]
88            fn try_from(value: FBigRepr<B>) -> Result<Self, Self::Error> {
89                Repr::try_from(value).map(|repr| $t(repr.$reduce()))
90            }
91        }
92
93        impl<R: Round, const B: Word> TryFrom<FBig<R, B>> for $t {
94            type Error = ConversionError;
95            #[inline]
96            fn try_from(value: FBig<R, B>) -> Result<Self, Self::Error> {
97                Repr::try_from(value).map(|repr| $t(repr.$reduce()))
98            }
99        }
100    };
101}
102forward_conversion_to_repr!(RBig, reduce);
103forward_conversion_to_repr!(Relaxed, reduce2);
104
105impl Repr {
106    // There are some cases where the result is exactly representable by a FBig
107    // without loss of significance (it's an integer or its denominator is a power of B).
108    // However, it's better to explicitly prohibit it because it's still failing
109    // in other cases and a method that panics occasionally is not good.
110    fn to_float<R: Round, const B: Word>(&self, precision: usize) -> Rounded<FBig<R, B>> {
111        assert!(precision > 0);
112
113        if self.numerator.is_zero() {
114            return FBig::ZERO.with_precision(precision);
115        }
116
117        let base = UBig::from_word(B);
118        let num_digits = self.numerator.ilog(&base);
119        let den_digits = self.denominator.ilog(&base);
120
121        let shift;
122        let (q, r) = if num_digits >= precision + den_digits {
123            shift = 0;
124            (&self.numerator).div_rem(&self.denominator)
125        } else {
126            shift = (precision + den_digits) - num_digits;
127            if B == 2 {
128                (&self.numerator << shift).div_rem(&self.denominator)
129            } else {
130                (&self.numerator * base.pow(shift)).div_rem(&self.denominator)
131            }
132        };
133        let rounded = if r.is_zero() {
134            Approximation::Exact(q)
135        } else {
136            let adjust = R::round_ratio(&q, r, self.denominator.as_ibig());
137            Approximation::Inexact(q + adjust, adjust)
138        };
139
140        let context = Context::<R>::new(precision);
141        rounded
142            .and_then(|n| context.convert_int(n))
143            .map(|f| f >> (shift as isize))
144    }
145}
146
147impl RBig {
148    /// Convert the rational number to a [FBig] with guaranteed correct rounding.
149    ///
150    /// # Examples
151    ///
152    /// ```
153    /// # use dashu_base::Approximation::*;
154    /// # use dashu_ratio::RBig;
155    /// use dashu_float::{DBig, round::Rounding::*};
156    ///
157    /// assert_eq!(RBig::ONE.to_float(1), Exact(DBig::ONE));
158    /// assert_eq!(RBig::from(1000).to_float(4), Exact(DBig::from(1000)));
159    /// assert_eq!(RBig::from_parts(1000.into(), 6u8.into()).to_float(4),
160    ///     Inexact(DBig::from_parts(1667.into(), -1), AddOne));
161    /// ```
162    #[inline]
163    pub fn to_float<R: Round, const B: Word>(&self, precision: usize) -> Rounded<FBig<R, B>> {
164        self.0.to_float(precision)
165    }
166
167    /// # Examples
168    ///
169    /// ```
170    /// # use dashu_base::ParseError;
171    /// # use dashu_ratio::RBig;
172    /// use dashu_float::DBig;
173    ///
174    /// let f = DBig::from_str_native("4.00")? / DBig::from_str_native("3.00")?;
175    /// let r = RBig::from_str_radix("4/3", 10)?;
176    /// assert_eq!(RBig::simplest_from_float(&f), Some(r));
177    /// assert_eq!(RBig::simplest_from_float(&DBig::INFINITY), None);
178    ///
179    /// # Ok::<(), ParseError>(())
180    /// ```
181    pub fn simplest_from_float<R: ErrorBounds, const B: Word>(f: &FBig<R, B>) -> Option<Self> {
182        if f.repr().is_infinite() {
183            return None;
184        } else if f.repr().is_zero() {
185            return Some(Self::ZERO);
186        }
187
188        // calculate lower and upper bound
189        let (l, r, incl_l, incl_r) = R::error_bounds(f);
190        let lb = f - l.with_precision(f.precision() + 1).unwrap();
191        let rb = f + r.with_precision(f.precision() + 1).unwrap();
192
193        // select the simplest in this range
194        let left = Self::try_from(lb).unwrap();
195        let right = Self::try_from(rb).unwrap();
196        let mut simplest = Self::simplest_in(left.clone(), right.clone());
197        if incl_l && left.is_simpler_than(&simplest) {
198            simplest = left;
199        }
200        if incl_r && right.is_simpler_than(&simplest) {
201            simplest = right;
202        }
203        Some(simplest)
204    }
205}
206
207impl Relaxed {
208    /// Convert the rational number to a [FBig] with guaranteed correct rounding.
209    ///
210    /// See [RBig::to_float] for details.
211    #[inline]
212    pub fn to_float<R: Round, const B: Word>(&self, precision: usize) -> Rounded<FBig<R, B>> {
213        self.0.to_float(precision)
214    }
215}