dashu_float/
round_ops.rs

1use crate::{
2    error::assert_finite,
3    fbig::FBig,
4    repr::{Context, Repr},
5    round::{mode, Round},
6    utils::{shr_digits, split_digits, split_digits_ref},
7};
8use dashu_base::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, fraction precision).
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        if self.repr.smaller_than_one() {
56            return (IBig::ZERO, self.repr.significand.clone(), self.context.precision);
57        }
58
59        let shift = (-self.repr.exponent) as usize;
60        let (hi, lo) = split_digits_ref::<B>(&self.repr.significand, shift);
61        (hi, lo, shift)
62    }
63
64    /// Split the rational number into integral and fractional parts (split at the radix point)
65    ///
66    /// It's equivalent to `(self.trunc(), self.fract())`
67    ///
68    /// # Examples
69    ///
70    /// ```
71    /// # use core::str::FromStr;
72    /// # use dashu_base::ParseError;
73    /// # use dashu_float::DBig;
74    /// let a = DBig::from_str("1.234")?;
75    /// let (trunc, fract) = a.split_at_point();
76    /// assert_eq!(trunc, DBig::from_str("1.0")?);
77    /// assert_eq!(fract, DBig::from_str("0.234")?);
78    /// // the actual precision of the fractional part is 3 digits
79    /// assert_eq!(trunc.precision(), 1);
80    /// assert_eq!(fract.precision(), 3);
81    /// # Ok::<(), ParseError>(())
82    /// ```
83    pub fn split_at_point(self) -> (Self, Self) {
84        // trivial case when the exponent is positive
85        if self.repr.exponent >= 0 {
86            return (self, Self::ZERO);
87        } else if self.repr.smaller_than_one() {
88            return (Self::ZERO, self);
89        }
90
91        let shift = (-self.repr.exponent) as usize;
92        let (hi, lo) = split_digits::<B>(self.repr.significand, shift);
93        let hi_ctxt = Context::new(self.context.precision.saturating_sub(shift));
94        let lo_ctxt = Context::new(shift);
95        (
96            FBig::new(Repr::new(hi, 0), hi_ctxt),
97            FBig::new(Repr::new(lo, self.repr.exponent), lo_ctxt),
98        )
99    }
100
101    /// Get the fractional part of the float
102    ///
103    /// **Note**: this function will adjust the precision accordingly!
104    ///
105    /// # Examples
106    ///
107    /// ```
108    /// # use core::str::FromStr;
109    /// # use dashu_base::ParseError;
110    /// # use dashu_float::DBig;
111    /// let a = DBig::from_str("1.234")?;
112    /// assert_eq!(a.fract(), DBig::from_str("0.234")?);
113    /// // the actual precision of the fractional part is 3 digits
114    /// assert_eq!(a.fract().precision(), 3);
115    /// # Ok::<(), ParseError>(())
116    /// ```
117    ///
118    /// # Panics
119    ///
120    /// Panics if the number is infinte
121    #[inline]
122    pub fn fract(&self) -> Self {
123        assert_finite(&self.repr);
124        if self.repr.exponent >= 0 {
125            return Self::ZERO;
126        }
127
128        let (_, lo, precision) = self.split_at_point_internal();
129        let context = Context::new(precision);
130        FBig::new(Repr::new(lo, self.repr.exponent), context)
131    }
132
133    /// Returns the smallest integer greater than or equal to self.
134    ///
135    /// See [FBig::round] for how the output precision is determined.
136    ///
137    /// # Examples
138    ///
139    /// ```
140    /// # use core::str::FromStr;
141    /// # use dashu_base::ParseError;
142    /// # use dashu_float::DBig;
143    /// let a = DBig::from_str("1.234")?;
144    /// assert_eq!(a.ceil(), DBig::from_str("2")?);
145    ///
146    /// // works for very large exponent
147    /// let b = DBig::from_str("1.234e10000")?;
148    /// assert_eq!(b.ceil(), b);
149    /// # Ok::<(), ParseError>(())
150    /// ```
151    ///
152    /// # Panics
153    ///
154    /// Panics if the number is infinte
155    #[inline]
156    pub fn ceil(&self) -> Self {
157        assert_finite(&self.repr);
158        if self.repr.is_zero() || self.repr.exponent >= 0 {
159            return self.clone();
160        } else if self.repr.smaller_than_one() {
161            return match self.repr.sign() {
162                Sign::Positive => Self::ONE,
163                Sign::Negative => Self::ZERO,
164            };
165        }
166
167        let (hi, lo, precision) = self.split_at_point_internal();
168        let rounding = mode::Up::round_fract::<B>(&hi, lo, precision);
169        let context = Context::new(self.context.precision.saturating_sub(precision));
170        FBig::new(Repr::new(hi + rounding, 0), context)
171    }
172
173    /// Returns the largest integer less than or equal to self.
174    ///
175    /// See [FBig::round] for how the output precision is determined.
176    ///
177    /// # Examples
178    ///
179    /// ```
180    /// # use core::str::FromStr;
181    /// # use dashu_base::ParseError;
182    /// # use dashu_float::DBig;
183    /// let a = DBig::from_str("1.234")?;
184    /// assert_eq!(a.floor(), DBig::from_str("1")?);
185    ///
186    /// // works for very large exponent
187    /// let b = DBig::from_str("1.234e10000")?;
188    /// assert_eq!(b.floor(), b);
189    /// # Ok::<(), ParseError>(())
190    /// ```
191    ///
192    /// # Panics
193    ///
194    /// Panics if the number is infinte
195    #[inline]
196    pub fn floor(&self) -> Self {
197        assert_finite(&self.repr);
198        if self.repr.exponent >= 0 {
199            return self.clone();
200        } else if self.repr.smaller_than_one() {
201            return match self.repr.sign() {
202                Sign::Positive => Self::ZERO,
203                Sign::Negative => Self::NEG_ONE,
204            };
205        }
206
207        let (hi, lo, precision) = self.split_at_point_internal();
208        let rounding = mode::Down::round_fract::<B>(&hi, lo, precision);
209        let context = Context::new(self.context.precision.saturating_sub(precision));
210        FBig::new(Repr::new(hi + rounding, 0), context)
211    }
212
213    /// Returns the integer nearest to self.
214    ///
215    /// If there are two integers equally close, then the one farther from zero is chosen.
216    ///
217    /// # Examples
218    ///
219    /// ```
220    /// # use core::str::FromStr;
221    /// # use dashu_base::ParseError;
222    /// # use dashu_float::DBig;
223    /// let a = DBig::from_str("1.234")?;
224    /// assert_eq!(a.round(), DBig::from_str("1")?);
225    ///
226    /// // works for very large exponent
227    /// let b = DBig::from_str("1.234e10000")?;
228    /// assert_eq!(b.round(), b);
229    /// # Ok::<(), ParseError>(())
230    /// ```
231    ///
232    /// # Precision
233    ///
234    /// If `self` is an integer, the result will have the same precision as `self`.
235    /// If `self` has fractional part, then the precision will be subtracted by the digits
236    /// in the fractional part. Examples:
237    /// * `1.00e100` (precision = 3) rounds to `1.00e100` (precision = 3)
238    /// * `1.234` (precision = 4) rounds to `1.` (precision = 1)
239    /// * `1.234e-10` (precision = 4) rounds to `0.` (precision = 0, i.e arbitrary precision)
240    ///
241    /// # Panics
242    ///
243    /// Panics if the number is infinte
244    pub fn round(&self) -> Self {
245        assert_finite(&self.repr);
246        if self.repr.exponent >= 0 {
247            return self.clone();
248        } else if self.repr.exponent + (self.repr.digits_ub() as isize) < -2 {
249            // to determine if the number rounds to zero, we need to make sure |self| < 0.5
250            // which is stricter than `self.repr.smaller_than_one()`
251            return Self::ZERO;
252        }
253
254        let (hi, lo, precision) = self.split_at_point_internal();
255        let rounding = mode::HalfAway::round_fract::<B>(&hi, lo, precision);
256        let context = Context::new(self.context.precision.saturating_sub(precision));
257        FBig::new(Repr::new(hi + rounding, 0), context)
258    }
259}