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}