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