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}