dashu_ratio/
simplify.rs

1//! Implementations of methods related to simplification.
2//!
3//! Note that these methods are only implemented for [RBig] but not for [Relaxed][crate::Relaxed]
4//! because the latter one is naturally not in the simplest form.
5
6use crate::{error::panic_divide_by_0, rbig::RBig, repr::Repr};
7use core::{cmp::Ordering, mem};
8use dashu_base::{AbsOrd, Approximation, DivRem, UnsignedAbs};
9use dashu_int::{IBig, Sign, UBig};
10
11impl Repr {
12    /// Find the simplest rational number in the open interval `(lower, upper)`.
13    /// See [RBig::simplest_in()] and <https://stackoverflow.com/q/66980340/5960776>.
14    pub fn simplest_in(mut lower: Self, mut upper: Self) -> Self {
15        let sign = if lower.numerator.sign() != upper.numerator.sign() {
16            // if lower < 0 < upper, then 0 is the simplest
17            return Self::zero();
18        } else {
19            lower.numerator.sign()
20        };
21        lower = lower.abs();
22        upper = upper.abs();
23
24        match lower.cmp(&upper) {
25            // swap so that lower is less than upper
26            Ordering::Greater => mem::swap(&mut lower, &mut upper),
27            Ordering::Equal => return sign * lower,
28            Ordering::Less => {}
29        }
30
31        let Repr {
32            numerator: mut num_l,
33            denominator: den_l,
34        } = lower;
35        let Repr {
36            numerator: mut num_r,
37            denominator: den_r,
38        } = upper;
39
40        // negative values might exist during the calculation
41        let (mut den_l, mut den_r) = (IBig::from(den_l), IBig::from(den_r));
42
43        // use continued fraction expansion to find this float
44        let (mut n0, mut d0) = (IBig::ONE, IBig::ZERO);
45        let (mut n1, mut d1) = (IBig::ZERO, IBig::ONE);
46        let (num, den) = loop {
47            let (q, r1) = num_l.div_rem(&den_l);
48
49            n1 += &q * &n0;
50            mem::swap(&mut n0, &mut n1);
51            d1 += &q * &d0;
52            mem::swap(&mut d0, &mut d1);
53
54            let r2 = mem::take(&mut num_r) - q * &den_r;
55            num_l = mem::replace(&mut den_r, r1);
56            num_r = mem::replace(&mut den_l, r2);
57
58            if num_l < den_l {
59                break (n0 + n1, d0 + d1);
60            }
61        };
62
63        debug_assert!(num.sign() == den.sign());
64        Repr {
65            numerator: num.unsigned_abs() * sign,
66            denominator: den.unsigned_abs(),
67        }
68    }
69}
70
71/// Implementation of simplest_from_f32, simplest_from_f64
72macro_rules! impl_simplest_from_float {
73    ($f:ident) => {{
74        if $f.is_infinite() || $f.is_nan() {
75            return None;
76        } else if $f == 0. {
77            return Some(Self::ZERO);
78        }
79
80        // get the range (f - ulp/2, f + ulp/2)
81        // if f is negative, then range will be flipped by simplest_in()
82        let mut est = Repr::try_from($f).unwrap();
83        est.numerator <<= 1;
84        est.denominator <<= 1;
85        let left = Self(
86            Repr {
87                numerator: &est.numerator + IBig::ONE,
88                denominator: est.denominator.clone(),
89            }
90            .reduce(),
91        );
92        let right = Self(
93            Repr {
94                numerator: est.numerator - IBig::ONE,
95                denominator: est.denominator,
96            }
97            .reduce(),
98        );
99
100        // find the simplest float in the range
101        let mut simplest = Self::simplest_in(left.clone(), right.clone());
102        if $f.to_bits() & 1 == 0 {
103            // consider boundry values when last bit is 0 (because ties to even)
104            if left.is_simpler_than(&simplest) {
105                simplest = left;
106            }
107            if right.is_simpler_than(&simplest) {
108                simplest = right;
109            }
110        }
111        Some(simplest)
112    }};
113}
114
115impl RBig {
116    /// Determine if this rational number is simpler than the other number.
117    ///
118    /// This method only make sense for canonicalized ratios.
119    #[inline]
120    pub fn is_simpler_than(&self, other: &Self) -> bool {
121        (self.denominator() < other.denominator()) // first compare denominator
122            && self.numerator().abs_cmp(other.numerator()).is_le() // then compare numerator
123            && self.sign() > other.sign() // then compare sign
124    }
125
126    /// Find the simplest rational number in the rounding interval of the [f32] number.
127    ///
128    /// This method returns None when the floating point value is not representable by a rational number,
129    /// such as infinities or nans.
130    ///
131    /// See [RBig::simplest_in] for the definition of `simplicity`.
132    ///
133    /// The rounding interval of a [f32] value is an interval such that all numbers in this
134    /// range will rounded to this [f32] value. For example the rounding interval for `1f32`
135    /// is `[1. - f32::EPSILON / 2, 1. + f32::EPSILON / 2]`. That is, the error of result value will
136    /// be less than 1/2 ULP.
137    ///
138    /// This method can be used to recover the original fraction represented as a division of [f32].
139    /// See the examples below.
140    ///
141    /// # Examples
142    ///
143    /// ```
144    /// # use dashu_ratio::RBig;
145    /// assert_eq!(
146    ///     RBig::simplest_from_f32(1e-1).unwrap(),
147    ///     RBig::from_parts(1.into(), 10u8.into())
148    /// );
149    /// assert_eq!(
150    ///     RBig::simplest_from_f32(22./7.).unwrap(),
151    ///     RBig::from_parts(22.into(), 7u8.into())
152    /// );
153    /// ```
154    pub fn simplest_from_f32(f: f32) -> Option<Self> {
155        impl_simplest_from_float!(f)
156    }
157
158    /// Find the simplest rational number in the rounding interval of the [f64] number.
159    ///
160    /// This method returns None when the floating point value is not representable by a rational number,
161    /// such as infinities or nans.
162    ///
163    /// See [RBig::simplest_in] for the definition of `simplicity`.
164    ///
165    /// The rounding interval of a [f64] value is an interval such that all numbers in this
166    /// range will rounded to this [f64] value. For example the rounding interval for `1f64`
167    /// is `[1. - f64::EPSILON / 2, 1. + f64::EPSILON / 2]`. That is, the error of result value will
168    /// be less than 1/2 ULP.
169    ///
170    /// This method can be used to recover the original fraction represented as a division of [f64].
171    /// See the examples below.
172    ///
173    /// # Examples
174    ///
175    /// ```
176    /// # use dashu_ratio::RBig;
177    /// assert_eq!(
178    ///     RBig::simplest_from_f64(1e-1).unwrap(),
179    ///     RBig::from_parts(1.into(), 10u8.into())
180    /// );
181    /// assert_eq!(
182    ///     RBig::simplest_from_f64(22./7.).unwrap(),
183    ///     RBig::from_parts(22.into(), 7u8.into())
184    /// );
185    pub fn simplest_from_f64(f: f64) -> Option<Self> {
186        impl_simplest_from_float!(f)
187    }
188
189    /// Find the simplest rational number in the open interval `(lower, upper)`.
190    ///
191    /// A rational `n₁/d₁` is simpler than another rational number `n₂/d₂` if:
192    /// * `d₁ < d₂` (compare denominator)
193    /// * or `|n₁| < |n₂|` (then compare the magnitude of numerator)
194    /// * or `n₂ < 0 < n₁` (then compare the sign)
195    ///
196    /// `lower` and `upper` will be swapped if necessary. If `lower` and `upper` are
197    /// the same number, then this number will be directly returned.
198    ///
199    /// # Examples
200    ///
201    /// ```
202    /// # use dashu_ratio::RBig;
203    /// let a = RBig::from_parts(1234.into(), 5678u16.into());
204    /// let b = RBig::from_parts(1235.into(), 5679u16.into());
205    /// let s = RBig::simplest_in(a, b);
206    /// // 1234/5678 < 5/23 < 1235/5679
207    /// assert_eq!(s, RBig::from_parts(5.into(), 23u8.into()));
208    /// ```
209    ///
210    #[inline]
211    pub fn simplest_in(lower: Self, upper: Self) -> Self {
212        Self(Repr::simplest_in(lower.0, upper.0).reduce())
213    }
214
215    /// Find the previous and next value in farey sequence (from 0 to 1) with `limit` as the order.
216    ///
217    /// This function requires `-1 < x < 1` and `x.denominator` > `limit`
218    fn farey_neighbors(x: &Self, limit: &UBig) -> (Self, Self) {
219        debug_assert!(x.denominator() > limit);
220        debug_assert!(!x.numerator().is_zero());
221        debug_assert!(x.numerator().abs_cmp(x.denominator()).is_le());
222
223        let (mut left, mut right) = match x.sign() {
224            Sign::Positive => (Repr::zero(), Repr::one()),
225            Sign::Negative => (Repr::neg_one(), Repr::zero()),
226        };
227
228        // the Farey neighbors can be found directly by adding the
229        // numerator and denominator together, see <https://en.wikipedia.org/wiki/Farey_sequence#Farey_neighbours>.
230        loop {
231            let mut next = Repr {
232                numerator: &left.numerator + &right.numerator,
233                denominator: &left.denominator + &right.denominator,
234            };
235
236            // test if the denominator has exceeded the limit
237            if &next.denominator > limit {
238                next = next.reduce();
239                if &next.denominator > limit {
240                    return (Self(left), Self(right));
241                }
242            }
243
244            // tighten the bounds
245            if next > x.0 {
246                right = next;
247            } else {
248                left = next;
249            }
250        }
251    }
252
253    /// Find the closest rational number to this number with a limit of the denominator.
254    ///
255    /// If the denominator of this number is larger than the limit, then it returns the closest one
256    /// between `self.next_up()` and `self.next_down()` to `self`. If the denominator of this number
257    /// is already less than or equal to the limit, then `Exact(self)` will be returned.
258    ///
259    /// The error `|self - self.nearest()|` will be less than `1/(2*limit)`, and the sign of
260    /// the error `self - self.nearest()` will be returned if the result is not [Exact][Approximation::Exact].
261    ///
262    /// # Examples
263    ///
264    /// ```
265    /// # use dashu_base::{Approximation::*, Sign};
266    /// # use dashu_ratio::RBig;
267    /// let a: RBig = 3.141592653.try_into().unwrap();
268    /// assert_eq!(a.nearest(&10u8.into()), Inexact(
269    ///     RBig::from_parts(22.into(), 7u8.into()),
270    ///     Sign::Positive // 22/7 > 3.141592653
271    /// ));
272    /// ```
273    pub fn nearest(&self, limit: &UBig) -> Approximation<Self, Sign> {
274        if limit.is_zero() {
275            panic_divide_by_0()
276        }
277
278        // directly return this number if it's already simple enough
279        if self.denominator() <= limit {
280            return Approximation::Exact(self.clone());
281        }
282
283        let (trunc, r) = self.clone().split_at_point();
284        let (left, right) = Self::farey_neighbors(&r, limit);
285
286        // find the closest one (compare r - left and right - r)
287        let mut mid = (&left + &right).0;
288        mid.denominator <<= 1;
289        if r.0 > mid {
290            Approximation::Inexact(trunc + right, Sign::Positive)
291        } else {
292            Approximation::Inexact(trunc + left, Sign::Negative)
293        }
294    }
295
296    /// Find the closest rational number that is greater than this number and has a denominator less than `limit`.
297    ///
298    /// It's equivalent to finding the next element in Farey sequence of order `limit`. The error
299    /// `|self - self.next_up()|` will be less than `1/limit`.
300    ///
301    /// # Examples
302    ///
303    /// ```
304    /// # use dashu_ratio::RBig;
305    /// let a: RBig = 3.141592653.try_into().unwrap();
306    /// assert_eq!(a.next_up(&10u8.into()), RBig::from_parts(22.into(), 7u8.into()));
307    /// ```
308    pub fn next_up(&self, limit: &UBig) -> Self {
309        if limit.is_zero() {
310            panic_divide_by_0()
311        }
312
313        let (trunc, fract) = self.clone().split_at_point();
314        let up = if self.denominator() <= limit {
315            // If the denominator of the number is already small enough, increase the number a little
316            // bit before finding the farey neighbors. Note that the distance between two adjacent
317            // numbers in a farey sequence is at least limit^-2, so we just increase limit^-2
318            let target = fract
319                + Self(Repr {
320                    numerator: IBig::ONE,
321                    denominator: limit.sqr(),
322                });
323            Self::farey_neighbors(&target, limit).1
324        } else {
325            // otherwise we can directly find the next value by finding farey bounds
326            Self::farey_neighbors(&fract, limit).1
327        };
328        trunc + up
329    }
330
331    /// Find the closest rational number that is less than this number and has a denominator less than `limit`.
332    ///
333    /// It's equivalent to finding the previous element in Farey sequence of order `limit`. The error
334    /// `|self - self.next_down()|` will be less than `1/limit`.
335    ///
336    /// # Examples
337    ///
338    /// ```
339    /// # use dashu_ratio::RBig;
340    /// let a: RBig = 3.141592653.try_into().unwrap();
341    /// assert_eq!(a.next_down(&10u8.into()), RBig::from_parts(25.into(), 8u8.into()));
342    /// ```
343    pub fn next_down(&self, limit: &UBig) -> Self {
344        if limit.is_zero() {
345            panic_divide_by_0()
346        }
347
348        // similar to next_up()
349        let (trunc, fract) = self.clone().split_at_point();
350        let down = if self.denominator() <= limit {
351            let target = fract
352                - Self(Repr {
353                    numerator: IBig::ONE,
354                    denominator: limit.sqr(),
355                });
356            Self::farey_neighbors(&target, limit).0
357        } else {
358            Self::farey_neighbors(&fract, limit).0
359        };
360        trunc + down
361    }
362}