Skip to main content

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}