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}