dashu_float/fbig.rs
1use crate::{
2 error::panic_unlimited_precision,
3 repr::{Context, Repr, Word},
4 round::{mode, Round},
5 utils::digit_len,
6};
7use dashu_base::Sign;
8use dashu_int::{DoubleWord, IBig};
9
10/// An arbitrary precision floating point number with arbitrary base and rounding mode.
11///
12/// An `FBig` is a [`Repr`] (the value: significand × base<sup>exponent</sup>) paired with a
13/// [`Context`] (the precision cap and rounding mode). Arithmetic follows the associated context;
14/// use the [`Context`] methods directly when you need a different precision/rounding, or to receive
15/// the rounding direction and errors instead of a panic.
16///
17/// The generic parameters are `BASE` (`B`, in `[2, isize::MAX]`) and `RoundingMode` (`R`, chosen from
18/// the [`mode`] module). With the defaults the number is base 2 rounded towards zero (the most
19/// efficient format); [`DBig`](crate::DBig) aliases base 10 rounded to nearest.
20///
21/// Binary operators require both operands to share the same base and rounding mode (no hidden
22/// conversion is performed); comparison allows differing rounding modes but not differing bases.
23///
24/// See the [user guide](https://zyxin.xyz/dashu/types.html) for the
25/// memory layout, and the
26/// [construction](https://zyxin.xyz/dashu/construct.html),
27/// [parsing & printing](https://zyxin.xyz/dashu/io/parse.html),
28/// [IEEE 754 compliance](https://zyxin.xyz/dashu/compliance.html), and
29/// [conversion](https://zyxin.xyz/dashu/convert.html) pages for those
30/// topics. (Notably: `FBig` has no NaN, supports IEEE-754 signed zero, and treats infinities as
31/// terminal values.) The accepted string format is documented on the [`core::str::FromStr`] impl.
32///
33/// # Examples
34///
35/// ```
36/// # use dashu_base::ParseError;
37/// # use dashu_float::DBig;
38/// use core::str::FromStr;
39///
40/// // parsing
41/// let a = DBig::from_parts(123456789.into(), -5);
42/// let b = DBig::from_str("1234.56789")?;
43/// let c = DBig::from_str("1.23456789e3")?;
44/// assert_eq!(a, b);
45/// assert_eq!(b, c);
46///
47/// // printing
48/// assert_eq!(format!("{}", DBig::from_str("12.34")?), "12.34");
49/// let x = DBig::from_str("10.01")?
50/// .with_precision(0) // use unlimited precision
51/// .value();
52/// if dashu_int::Word::BITS == 64 {
53/// // number of digits to display depends on the word size
54/// assert_eq!(
55/// format!("{:?}", x.powi(100.into())),
56/// "1105115697720767968..1441386704950100001 * 10 ^ -200 (prec: 0)"
57/// );
58/// }
59/// # Ok::<(), ParseError>(())
60/// ```
61#[cfg_attr(
62 feature = "rkyv_v07",
63 derive(rkyv_v07::Archive, rkyv_v07::Serialize, rkyv_v07::Deserialize)
64)]
65// `rkyv_v07` and `rkyv_v08` are mutually exclusive; when both are enabled (e.g. `--all-features`)
66// the 0.7 derive wins so the two versions' generated `Archived*`/`*Resolver` type names don't collide.
67#[cfg_attr(
68 all(feature = "rkyv_v08", not(feature = "rkyv_v07")),
69 derive(rkyv_v08::Archive, rkyv_v08::Serialize, rkyv_v08::Deserialize)
70)]
71#[cfg_attr(all(feature = "rkyv_v08", not(feature = "rkyv_v07")), rkyv(crate = rkyv_v08))]
72pub struct FBig<RoundingMode: Round = mode::Zero, const BASE: Word = 2> {
73 pub(crate) repr: Repr<BASE>,
74 pub(crate) context: Context<RoundingMode>,
75}
76
77impl<R: Round, const B: Word> FBig<R, B> {
78 /// Create a [FBig] instance from raw parts, internal use only
79 #[inline]
80 pub(crate) const fn new(repr: Repr<B>, context: Context<R>) -> Self {
81 Self { repr, context }
82 }
83
84 /// Create a [FBig] instance from [Repr] and [Context].
85 ///
86 /// This method should not be used in most cases. It's designed to be used when
87 /// you hold a [Repr] instance and want to create an [FBig] from that.
88 ///
89 /// # Examples
90 ///
91 /// ```
92 /// # use dashu_float::DBig;
93 /// use dashu_float::{Repr, Context};
94 ///
95 /// assert_eq!(DBig::from_repr(Repr::one(), Context::new(1)), DBig::ONE);
96 /// assert_eq!(DBig::from_repr(Repr::infinity(), Context::new(1)), DBig::INFINITY);
97 /// ```
98 ///
99 /// # Panics
100 ///
101 /// Panics if the [Repr] has more digits than `precision + 1` (the one allowed guard digit from
102 /// an inexact add/sub — see [`Repr`]). Note that this condition is not checked in release builds.
103 #[inline]
104 pub fn from_repr(repr: Repr<B>, context: Context<R>) -> Self {
105 debug_assert!(
106 repr.is_infinite() || !context.is_limited() || repr.digits() <= context.precision + 1
107 );
108 Self { repr, context }
109 }
110
111 /// Create a [FBig] instance from [Repr]. Due to the limitation of const operations,
112 /// the precision of the float is set to unlimited.
113 ///
114 /// # Examples
115 ///
116 /// ```
117 /// # use dashu_float::DBig;
118 /// use dashu_float::{Repr, Context};
119 ///
120 /// assert_eq!(DBig::from_repr_const(Repr::one()), DBig::ONE);
121 /// assert_eq!(DBig::from_repr_const(Repr::infinity()), DBig::INFINITY);
122 /// ```
123 #[inline]
124 pub const fn from_repr_const(repr: Repr<B>) -> Self {
125 Self {
126 repr,
127 context: Context::new(0),
128 }
129 }
130
131 /// [FBig] with value 0 and unlimited precision
132 ///
133 /// To test if the float number is `+0`, use `self.repr().is_pos_zero()` (or
134 /// `self.repr().significand().is_zero()` to detect either signed zero).
135 pub const ZERO: Self = Self::new(Repr::zero(), Context::new(0));
136
137 /// [FBig] with value 1 and unlimited precision
138 ///
139 /// To test if the float number is one, use `self.repr().is_one()`.
140 pub const ONE: Self = Self::new(Repr::one(), Context::new(0));
141
142 /// [FBig] with value -1 and unlimited precision
143 pub const NEG_ONE: Self = Self::new(Repr::neg_one(), Context::new(0));
144
145 /// [FBig] instance representing the positive infinity (+∞)
146 ///
147 /// To test if the float number is infinite, use `self.repr().infinite()`.
148 pub const INFINITY: Self = Self::new(Repr::infinity(), Context::new(0));
149
150 /// [FBig] instance representing the negative infinity (-∞)
151 ///
152 /// To test if the float number is infinite, use `self.repr().infinite()`.
153 pub const NEG_INFINITY: Self = Self::new(Repr::neg_infinity(), Context::new(0));
154
155 /// Get the maximum precision set for the float number.
156 ///
157 /// It's equivalent to `self.context().precision()`.
158 ///
159 /// # Examples
160 ///
161 /// ```
162 /// # use core::str::FromStr;
163 /// # use dashu_base::ParseError;
164 /// # use dashu_float::DBig;
165 /// # use dashu_int::IBig;
166 /// use dashu_float::Repr;
167 ///
168 /// let a = DBig::from_str("1.234")?;
169 /// assert!(a.repr().significand() <= &IBig::from(10).pow(a.precision()));
170 /// # Ok::<(), ParseError>(())
171 /// ```
172 #[inline]
173 pub const fn precision(&self) -> usize {
174 self.context.precision
175 }
176
177 /// Get the number of the significant digits in the float number
178 ///
179 /// It's equivalent to `self.repr().digits()`.
180 ///
181 /// This value is also the actual precision needed for the float number. Shrink to this
182 /// value using [with_precision()][FBig::with_precision] will not cause loss of float precision.
183 ///
184 /// # Examples
185 ///
186 /// ```
187 /// # use core::str::FromStr;
188 /// # use dashu_base::ParseError;
189 /// # use dashu_float::DBig;
190 /// use dashu_base::Approximation::*;
191 ///
192 /// let a = DBig::from_str("-1.234e-3")?;
193 /// assert_eq!(a.digits(), 4);
194 /// assert!(matches!(a.clone().with_precision(4), Exact(_)));
195 /// assert!(matches!(a.clone().with_precision(3), Inexact(_, _)));
196 /// # Ok::<(), ParseError>(())
197 /// ```
198 #[inline]
199 pub fn digits(&self) -> usize {
200 self.repr.digits()
201 }
202
203 /// Get the context associated with the float number
204 #[inline]
205 pub const fn context(&self) -> Context<R> {
206 self.context
207 }
208 /// Get a reference to the underlying numeric representation
209 #[inline]
210 pub const fn repr(&self) -> &Repr<B> {
211 &self.repr
212 }
213 /// Get the underlying numeric representation
214 ///
215 /// # Examples
216 ///
217 /// ```
218 /// # use dashu_float::DBig;
219 /// use dashu_float::Repr;
220 ///
221 /// let a = DBig::ONE;
222 /// assert_eq!(a.into_repr(), Repr::<10>::one());
223 /// ```
224 #[inline]
225 pub fn into_repr(self) -> Repr<B> {
226 self.repr
227 }
228
229 /// Convert raw parts (significand, exponent) into a float number.
230 ///
231 /// The precision will be inferred from significand (the lowest k such that `significand <= base^k`)
232 ///
233 /// # Examples
234 ///
235 /// ```
236 /// # use dashu_base::ParseError;
237 /// # use dashu_float::DBig;
238 /// use core::str::FromStr;
239 /// let a = DBig::from_parts((-1234).into(), -2);
240 /// assert_eq!(a, DBig::from_str("-12.34")?);
241 /// assert_eq!(a.precision(), 4); // 1234 has 4 (decimal) digits
242 /// # Ok::<(), ParseError>(())
243 /// ```
244 #[inline]
245 pub fn from_parts(significand: IBig, exponent: isize) -> Self {
246 let precision = digit_len::<B>(&significand).max(1); // set precision to 1 if signficand is zero
247 let repr = Repr::new(significand, exponent);
248 let context = Context::new(precision);
249 Self::new(repr, context)
250 }
251
252 /// Convert raw parts (significand, exponent) into a float number in a `const` context.
253 ///
254 /// It requires that the significand fits in a [DoubleWord].
255 ///
256 /// The precision will be inferred from significand (the lowest k such that `significand <= base^k`).
257 /// If the `min_precision` is provided, then the higher one from the given and inferred precision
258 /// will be used as the final precision.
259 ///
260 /// # Examples
261 ///
262 /// ```
263 /// # use dashu_base::ParseError;
264 /// # use dashu_float::DBig;
265 /// use core::str::FromStr;
266 /// use dashu_base::Sign;
267 ///
268 /// const A: DBig = DBig::from_parts_const(Sign::Negative, 1234, -2, None);
269 /// assert_eq!(A, DBig::from_str("-12.34")?);
270 /// assert_eq!(A.precision(), 4); // 1234 has 4 (decimal) digits
271 ///
272 /// const B: DBig = DBig::from_parts_const(Sign::Negative, 1234, -2, Some(5));
273 /// assert_eq!(B.precision(), 5); // overrided by the argument
274 /// # Ok::<(), ParseError>(())
275 /// ```
276 #[inline]
277 pub const fn from_parts_const(
278 sign: Sign,
279 significand: DoubleWord,
280 exponent: isize,
281 min_precision: Option<usize>,
282 ) -> Self {
283 if significand == 0 {
284 return Self::ZERO;
285 }
286
287 // The precision default is the significand's base-`B` digit count; the normalized `Repr`
288 // is built by `Repr::new_const` (which shares `normalize_word_const`).
289 let (_, _, digits) = crate::repr::normalize_word_const::<B>(significand, exponent);
290 let repr = Repr::new_const(sign, significand, exponent);
291 let precision = match min_precision {
292 Some(prec) if prec > digits => prec,
293 _ => digits,
294 };
295 Self::new(repr, Context::new(precision))
296 }
297
298 /// Return the value of the least significant digit of the float number x,
299 /// such that `x + ulp` is the first float number greater than x (given the precision from the context).
300 ///
301 /// # Examples
302 ///
303 /// ```
304 /// # use core::str::FromStr;
305 /// # use dashu_base::ParseError;
306 /// # use dashu_float::DBig;
307 /// assert_eq!(DBig::from_str("1.23")?.ulp(), DBig::from_str("0.01")?);
308 /// assert_eq!(DBig::from_str("01.23")?.ulp(), DBig::from_str("0.001")?);
309 /// # Ok::<(), ParseError>(())
310 /// ```
311 ///
312 /// # Panics
313 /// Panics if the precision of the number is 0 (unlimited).
314 ///
315 #[inline]
316 pub fn ulp(&self) -> Self {
317 if self.context.precision == 0 {
318 panic_unlimited_precision();
319 }
320 if self.repr.is_infinite() {
321 return self.clone();
322 }
323
324 let repr = Repr {
325 significand: IBig::ONE,
326 exponent: self
327 .repr
328 .exponent
329 .saturating_add(self.repr.digits() as isize)
330 .saturating_sub(self.context.precision as isize),
331 };
332 Self::new(repr, self.context)
333 }
334
335 /// A cheap lower bound on [`ulp`](Self::ulp), guaranteed strictly smaller than it.
336 ///
337 /// Unlike [`ulp`](Self::ulp), this uses the approximated lower bound
338 /// [`digits_lb`](crate::Repr::digits_lb) rather than the exact digit count, so
339 /// it is faster but only contracted as `< ulp()` (not a tight value). Its niche is as
340 /// a conservative *negligibility threshold* — e.g. terminating an iterative method
341 /// once a correction falls below it, which is the same purpose dashu's own series
342 /// loops use it for. For a rigorous error or radius bound, prefer [`ulp`](Self::ulp).
343 ///
344 /// # Panics
345 /// Panics if the precision is 0 (unlimited). Returns a clone for an infinite value
346 /// (matching [`ulp`](Self::ulp)).
347 #[inline]
348 pub fn ulp_lb(&self) -> Self {
349 if self.context.precision == 0 {
350 panic_unlimited_precision();
351 }
352 if self.repr.is_infinite() {
353 return self.clone();
354 }
355
356 let repr = Repr {
357 significand: IBig::ONE,
358 exponent: self
359 .repr
360 .exponent
361 .saturating_add(self.repr.digits_lb() as isize)
362 .saturating_sub(self.context.precision as isize)
363 .saturating_sub(1),
364 };
365 Self::new(repr, self.context)
366 }
367}
368
369// This custom implementation is necessary due to https://github.com/rust-lang/rust/issues/98374
370impl<R: Round, const B: Word> Clone for FBig<R, B> {
371 #[inline]
372 fn clone(&self) -> Self {
373 Self {
374 repr: self.repr.clone(),
375 context: self.context,
376 }
377 }
378
379 #[inline]
380 fn clone_from(&mut self, source: &Self) {
381 self.repr.clone_from(&source.repr);
382 self.context = source.context;
383 }
384}
385
386impl<R: Round, const B: Word> Default for FBig<R, B> {
387 /// Default value: 0.
388 #[inline]
389 fn default() -> Self {
390 Self::ZERO
391 }
392}
393
394#[cfg(test)]
395mod tests {
396 use super::*;
397 use crate::round::mode::HalfEven;
398
399 // `ulp()`/`ulp_lb()` compute the exponent as `e + digits - precision`. For a value whose
400 // exponent is near `isize::MIN` (e.g. a `powi` result at the edge of the representable range)
401 // that subtraction used to underflow `isize` and panic inside the Ziv containment test. The
402 // arithmetic is now saturating, so an extreme exponent yields a saturated (smallest-representable)
403 // ulp instead of panicking.
404 #[test]
405 fn ulp_extreme_exponent_does_not_overflow() {
406 // 1 × 2^(isize::MIN + 8) at precision 53: ulp exponent = (isize::MIN+8) + 1 - 53, which
407 // underflows isize::MIN without the saturating arithmetic.
408 let ctx = Context::<HalfEven>::new(53);
409 let v = FBig::new(Repr::<2>::new(IBig::ONE, isize::MIN + 8), ctx);
410 let _ = v.ulp(); // must not panic
411 let _ = v.ulp_lb(); // must not panic
412 // Same check near the top end of the exponent range.
413 let v = FBig::new(Repr::<2>::new(IBig::ONE, isize::MAX - 8), ctx);
414 let _ = v.ulp();
415 let _ = v.ulp_lb();
416 }
417}