Skip to main content

dashu_float/
round_ops.rs

1use crate::{
2    error::assert_finite,
3    fbig::FBig,
4    repr::{Context, Repr},
5    round::{mode, Round, Rounded},
6    utils::{shr_digits, split_digits, split_digits_ref},
7};
8use dashu_base::{Approximation::*, Sign};
9use dashu_int::{IBig, Word};
10
11impl<R: Round, const B: Word> FBig<R, B> {
12    /// Get the integral part of the float
13    ///
14    /// See [FBig::round] for how the output precision is determined.
15    ///
16    /// # Examples
17    ///
18    /// ```
19    /// # use core::str::FromStr;
20    /// # use dashu_base::ParseError;
21    /// # use dashu_float::DBig;
22    /// let a = DBig::from_str("1.234")?;
23    /// assert_eq!(a.trunc(), DBig::from_str("1")?);
24    /// // the actual precision of the integral part is 1 digit
25    /// assert_eq!(a.trunc().precision(), 1);
26    /// # Ok::<(), ParseError>(())
27    /// ```
28    ///
29    /// # Panics
30    ///
31    /// Panics if the number is infinte
32    #[inline]
33    pub fn trunc(&self) -> Self {
34        assert_finite(&self.repr);
35
36        if self.repr.exponent >= 0 {
37            return self.clone();
38        } else if self.repr.smaller_than_one() {
39            return Self::ZERO;
40        }
41
42        let shift = (-self.repr.exponent) as usize;
43        let signif = shr_digits::<B>(&self.repr.significand, shift);
44        let context = Context::new(self.context.precision.saturating_sub(shift));
45        FBig::new(Repr::new(signif, 0), context)
46    }
47
48    // Split the float number at the radix point, assuming it exists (the number is not a integer).
49    // The method returns (integral part, fractional part, fractional scale).
50    //
51    // Different from the public `split_at_point()` API, this method doesn't take the ownership of
52    // this number.
53    pub(crate) fn split_at_point_internal(&self) -> (IBig, IBig, usize) {
54        debug_assert!(self.repr.exponent < 0);
55        let shift = (-self.repr.exponent) as usize;
56        if self.repr.smaller_than_one() {
57            // For numbers smaller than 1, the integral part is zero and the stored
58            // significand is the whole fractional payload.
59            //
60            // The third return value is the fractional scale, i.e. the number of
61            // radix digits after the point. It must be -exponent, because callers
62            // such as round_fract use it as the denominator exponent B^scale.
63            //
64            // This is intentionally not self.context.precision: context precision is
65            // the significant-digit precision of the float, while this value describes
66            // the positional scale of the fractional part.
67            return (IBig::ZERO, self.repr.significand.clone(), shift);
68        }
69
70        let (hi, lo) = split_digits_ref::<B>(&self.repr.significand, shift);
71        (hi, lo, shift)
72    }
73
74    /// Split the rational number into integral and fractional parts (split at the radix point)
75    ///
76    /// It's equivalent to `(self.trunc(), self.fract())`
77    ///
78    /// # Examples
79    ///
80    /// ```
81    /// # use core::str::FromStr;
82    /// # use dashu_base::ParseError;
83    /// # use dashu_float::DBig;
84    /// let a = DBig::from_str("1.234")?;
85    /// let (trunc, fract) = a.split_at_point();
86    /// assert_eq!(trunc, DBig::from_str("1.0")?);
87    /// assert_eq!(fract, DBig::from_str("0.234")?);
88    /// // the actual precision of the fractional part is 3 digits
89    /// assert_eq!(trunc.precision(), 1);
90    /// assert_eq!(fract.precision(), 3);
91    /// # Ok::<(), ParseError>(())
92    /// ```
93    pub fn split_at_point(self) -> (Self, Self) {
94        // trivial case when the exponent is positive
95        if self.repr.exponent >= 0 {
96            return (self, Self::ZERO);
97        } else if self.repr.smaller_than_one() {
98            return (Self::ZERO, self);
99        }
100
101        let shift = (-self.repr.exponent) as usize;
102        let (hi, lo) = split_digits::<B>(self.repr.significand, shift);
103        let hi_ctxt = Context::new(self.context.precision.saturating_sub(shift));
104        let lo_ctxt = Context::new(shift);
105        (
106            FBig::new(Repr::new(hi, 0), hi_ctxt),
107            FBig::new(Repr::new(lo, self.repr.exponent), lo_ctxt),
108        )
109    }
110
111    /// Get the fractional part of the float
112    ///
113    /// **Note**: this function will adjust the precision accordingly!
114    ///
115    /// # Examples
116    ///
117    /// ```
118    /// # use core::str::FromStr;
119    /// # use dashu_base::ParseError;
120    /// # use dashu_float::DBig;
121    /// let a = DBig::from_str("1.234")?;
122    /// assert_eq!(a.fract(), DBig::from_str("0.234")?);
123    /// // the actual precision of the fractional part is 3 digits
124    /// assert_eq!(a.fract().precision(), 3);
125    /// # Ok::<(), ParseError>(())
126    /// ```
127    ///
128    /// # Panics
129    ///
130    /// Panics if the number is infinte
131    #[inline]
132    pub fn fract(&self) -> Self {
133        assert_finite(&self.repr);
134        if self.repr.exponent >= 0 {
135            return Self::ZERO;
136        } else if self.repr.smaller_than_one() {
137            return self.clone();
138        }
139
140        let (_, lo, precision) = self.split_at_point_internal();
141        let context = Context::new(precision);
142        FBig::new(Repr::new(lo, self.repr.exponent), context)
143    }
144
145    /// Returns the smallest integer greater than or equal to self.
146    ///
147    /// See [FBig::round] for how the output precision is determined.
148    ///
149    /// # Examples
150    ///
151    /// ```
152    /// # use core::str::FromStr;
153    /// # use dashu_base::ParseError;
154    /// # use dashu_float::DBig;
155    /// let a = DBig::from_str("1.234")?;
156    /// assert_eq!(a.ceil(), DBig::from_str("2")?);
157    ///
158    /// // works for very large exponent
159    /// let b = DBig::from_str("1.234e10000")?;
160    /// assert_eq!(b.ceil(), b);
161    /// # Ok::<(), ParseError>(())
162    /// ```
163    ///
164    /// # Panics
165    ///
166    /// Panics if the number is infinte
167    #[inline]
168    pub fn ceil(&self) -> Self {
169        assert_finite(&self.repr);
170        if self.repr.is_zero() || self.repr.exponent >= 0 {
171            return self.clone();
172        } else if self.repr.smaller_than_one() {
173            return match self.repr.sign() {
174                Sign::Positive => Self::ONE,
175                Sign::Negative => Self::ZERO,
176            };
177        }
178
179        let (hi, lo, precision) = self.split_at_point_internal();
180        let rounding = mode::Up::round_fract::<B>(&hi, lo, precision);
181        let context = Context::new(self.context.precision.saturating_sub(precision));
182        FBig::new(Repr::new(hi + rounding, 0), context)
183    }
184
185    /// Returns the largest integer less than or equal to self.
186    ///
187    /// See [FBig::round] for how the output precision is determined.
188    ///
189    /// # Examples
190    ///
191    /// ```
192    /// # use core::str::FromStr;
193    /// # use dashu_base::ParseError;
194    /// # use dashu_float::DBig;
195    /// let a = DBig::from_str("1.234")?;
196    /// assert_eq!(a.floor(), DBig::from_str("1")?);
197    ///
198    /// // works for very large exponent
199    /// let b = DBig::from_str("1.234e10000")?;
200    /// assert_eq!(b.floor(), b);
201    /// # Ok::<(), ParseError>(())
202    /// ```
203    ///
204    /// # Panics
205    ///
206    /// Panics if the number is infinte
207    #[inline]
208    pub fn floor(&self) -> Self {
209        assert_finite(&self.repr);
210        if self.repr.exponent >= 0 {
211            return self.clone();
212        } else if self.repr.smaller_than_one() {
213            return match self.repr.sign() {
214                Sign::Positive => Self::ZERO,
215                Sign::Negative => Self::NEG_ONE,
216            };
217        }
218
219        let (hi, lo, precision) = self.split_at_point_internal();
220        let rounding = mode::Down::round_fract::<B>(&hi, lo, precision);
221        let context = Context::new(self.context.precision.saturating_sub(precision));
222        FBig::new(Repr::new(hi + rounding, 0), context)
223    }
224
225    /// Returns the integer nearest to self.
226    ///
227    /// If there are two integers equally close, then the one farther from zero is chosen.
228    ///
229    /// # Examples
230    ///
231    /// ```
232    /// # use core::str::FromStr;
233    /// # use dashu_base::ParseError;
234    /// # use dashu_float::DBig;
235    /// let a = DBig::from_str("1.234")?;
236    /// assert_eq!(a.round(), DBig::from_str("1")?);
237    ///
238    /// // works for very large exponent
239    /// let b = DBig::from_str("1.234e10000")?;
240    /// assert_eq!(b.round(), b);
241    /// # Ok::<(), ParseError>(())
242    /// ```
243    ///
244    /// # Precision
245    ///
246    /// If `self` is an integer, the result will have the same precision as `self`.
247    /// If `self` has fractional part, then the precision will be subtracted by the digits
248    /// in the fractional part. Examples:
249    /// * `1.00e100` (precision = 3) rounds to `1.00e100` (precision = 3)
250    /// * `1.234` (precision = 4) rounds to `1.` (precision = 1)
251    /// * `1.234e-10` (precision = 4) rounds to `0.` (precision = 0, i.e arbitrary precision)
252    ///
253    /// # Panics
254    ///
255    /// Panics if the number is infinte
256    pub fn round(&self) -> Self {
257        assert_finite(&self.repr);
258        if self.repr.exponent >= 0 {
259            return self.clone();
260        } else if self.repr.exponent + (self.repr.digits_ub() as isize) < -2 {
261            // to determine if the number rounds to zero, we need to make sure |self| < 0.5
262            // which is stricter than `self.repr.smaller_than_one()`
263            return Self::ZERO;
264        }
265
266        let (hi, lo, precision) = self.split_at_point_internal();
267        let rounding = mode::HalfAway::round_fract::<B>(&hi, lo, precision);
268        let context = Context::new(self.context.precision.saturating_sub(precision));
269        FBig::new(Repr::new(hi + rounding, 0), context)
270    }
271
272    /// Round the number to the nearest multiple of `BASE^exp`.
273    ///
274    /// This is the dashu analog of Python's `Decimal.quantize()`. The result's
275    /// value is an exact multiple of `BASE^exp`, and its precision is set so that
276    /// [`ulp()`][FBig::ulp] equals `BASE^exp`. Because dashu floats are
277    /// normalized, trailing zeros are not preserved in storage (the stored
278    /// exponent may be coarser than `exp`), but the value and ULP are exact. The
279    /// result keeps `self`'s rounding mode.
280    ///
281    /// # Examples
282    ///
283    /// ```
284    /// # use core::str::FromStr;
285    /// # use dashu_base::ParseError;
286    /// # use dashu_float::DBig;
287    /// use dashu_base::Approximation::*;
288    /// use dashu_float::round::Rounding::*;
289    ///
290    /// let a = DBig::from_str("1.234")?; // precision 4
291    ///
292    /// // round to 2 fractional digits (exp = -2): 3 significant figures remain
293    /// assert_eq!(a.quantize(-2), Inexact(DBig::from_str("1.23")?, NoOp));
294    /// assert_eq!(a.quantize(-2).value().precision(), 3);
295    ///
296    /// // a finer quantum is exact (no rounding) and *increases* the precision
297    /// assert_eq!(a.quantize(-10), Exact(DBig::from_str("1.234")?));
298    /// assert_eq!(a.quantize(-10).value().precision(), 11);
299    ///
300    /// // round to integer (exp = 0), or to the nearest 1000 (exp = 3)
301    /// assert_eq!(a.quantize(0), Inexact(DBig::from_str("1")?, NoOp));
302    /// assert_eq!(DBig::from_str("999")?.quantize(3), Inexact(DBig::from_str("1000")?, AddOne));
303    /// # Ok::<(), ParseError>(())
304    /// ```
305    ///
306    /// # Panics
307    ///
308    /// Panics if the number is infinte
309    pub fn quantize(&self, exp: isize) -> Rounded<Self> {
310        assert_finite(&self.repr);
311        if self.repr.is_zero() {
312            return Exact(self.clone());
313        }
314
315        let self_exp = self.repr.exponent;
316        if exp <= self_exp {
317            // finer-or-equal quantum: self is already an exact multiple of BASE^exp,
318            // so only the precision changes (set so that ulp == BASE^exp).
319            let precision = (self_exp + self.repr.digits() as isize - exp) as usize;
320            return Exact(FBig::new(self.repr.clone(), Context::new(precision)));
321        }
322
323        // coarser quantum: round off (exp - self_exp) low-order digits.
324        // Because a normalized significand is never divisible by BASE, this branch
325        // is always inexact (its low part is never all-zero).
326        let shift = (exp - self_exp) as usize;
327        let (hi, lo) = split_digits_ref::<B>(&self.repr.significand, shift);
328        let adjust = R::round_fract::<B>(&hi, lo, shift);
329        let repr = Repr::new(hi + adjust, exp);
330        // precision is set so that ulp == BASE^exp; a result that rounds to zero
331        // has no meaningful ulp, so it gets unlimited precision (like `round()`).
332        let precision = if repr.is_zero() {
333            0
334        } else {
335            (repr.exponent + repr.digits() as isize - exp) as usize
336        };
337        Inexact(FBig::new(repr, Context::new(precision)), adjust)
338    }
339}