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/// ```
61pub struct FBig<RoundingMode: Round = mode::Zero, const BASE: Word = 2> {
62    pub(crate) repr: Repr<BASE>,
63    pub(crate) context: Context<RoundingMode>,
64}
65
66impl<R: Round, const B: Word> FBig<R, B> {
67    /// Create a [FBig] instance from raw parts, internal use only
68    #[inline]
69    pub(crate) const fn new(repr: Repr<B>, context: Context<R>) -> Self {
70        Self { repr, context }
71    }
72
73    /// Create a [FBig] instance from [Repr] and [Context].
74    ///
75    /// This method should not be used in most cases. It's designed to be used when
76    /// you hold a [Repr] instance and want to create an [FBig] from that.
77    ///
78    /// # Examples
79    ///
80    /// ```
81    /// # use dashu_float::DBig;
82    /// use dashu_float::{Repr, Context};
83    ///
84    /// assert_eq!(DBig::from_repr(Repr::one(), Context::new(1)), DBig::ONE);
85    /// assert_eq!(DBig::from_repr(Repr::infinity(), Context::new(1)), DBig::INFINITY);
86    /// ```
87    ///
88    /// # Panics
89    ///
90    /// Panics if the [Repr] has more digits than `precision + 1` (the one allowed guard digit from
91    /// an inexact add/sub — see [`Repr`]). Note that this condition is not checked in release builds.
92    #[inline]
93    pub fn from_repr(repr: Repr<B>, context: Context<R>) -> Self {
94        debug_assert!(
95            repr.is_infinite() || !context.is_limited() || repr.digits() <= context.precision + 1
96        );
97        Self { repr, context }
98    }
99
100    /// Create a [FBig] instance from [Repr]. Due to the limitation of const operations,
101    /// the precision of the float is set to unlimited.
102    ///
103    /// # Examples
104    ///
105    /// ```
106    /// # use dashu_float::DBig;
107    /// use dashu_float::{Repr, Context};
108    ///
109    /// assert_eq!(DBig::from_repr_const(Repr::one()), DBig::ONE);
110    /// assert_eq!(DBig::from_repr_const(Repr::infinity()), DBig::INFINITY);
111    /// ```
112    #[inline]
113    pub const fn from_repr_const(repr: Repr<B>) -> Self {
114        Self {
115            repr,
116            context: Context::new(0),
117        }
118    }
119
120    /// [FBig] with value 0 and unlimited precision
121    ///
122    /// To test if the float number is `+0`, use `self.repr().is_pos_zero()` (or
123    /// `self.repr().significand().is_zero()` to detect either signed zero).
124    pub const ZERO: Self = Self::new(Repr::zero(), Context::new(0));
125
126    /// [FBig] with value 1 and unlimited precision
127    ///
128    /// To test if the float number is one, use `self.repr().is_one()`.
129    pub const ONE: Self = Self::new(Repr::one(), Context::new(0));
130
131    /// [FBig] with value -1 and unlimited precision
132    pub const NEG_ONE: Self = Self::new(Repr::neg_one(), Context::new(0));
133
134    /// [FBig] instance representing the positive infinity (+∞)
135    ///
136    /// To test if the float number is infinite, use `self.repr().infinite()`.
137    pub const INFINITY: Self = Self::new(Repr::infinity(), Context::new(0));
138
139    /// [FBig] instance representing the negative infinity (-∞)
140    ///
141    /// To test if the float number is infinite, use `self.repr().infinite()`.
142    pub const NEG_INFINITY: Self = Self::new(Repr::neg_infinity(), Context::new(0));
143
144    /// Get the maximum precision set for the float number.
145    ///
146    /// It's equivalent to `self.context().precision()`.
147    ///
148    /// # Examples
149    ///
150    /// ```
151    /// # use core::str::FromStr;
152    /// # use dashu_base::ParseError;
153    /// # use dashu_float::DBig;
154    /// # use dashu_int::IBig;
155    /// use dashu_float::Repr;
156    ///
157    /// let a = DBig::from_str("1.234")?;
158    /// assert!(a.repr().significand() <= &IBig::from(10).pow(a.precision()));
159    /// # Ok::<(), ParseError>(())
160    /// ```
161    #[inline]
162    pub const fn precision(&self) -> usize {
163        self.context.precision
164    }
165
166    /// Get the number of the significant digits in the float number
167    ///
168    /// It's equivalent to `self.repr().digits()`.
169    ///
170    /// This value is also the actual precision needed for the float number. Shrink to this
171    /// value using [with_precision()][FBig::with_precision] will not cause loss of float precision.
172    ///
173    /// # Examples
174    ///
175    /// ```
176    /// # use core::str::FromStr;
177    /// # use dashu_base::ParseError;
178    /// # use dashu_float::DBig;
179    /// use dashu_base::Approximation::*;
180    ///
181    /// let a = DBig::from_str("-1.234e-3")?;
182    /// assert_eq!(a.digits(), 4);
183    /// assert!(matches!(a.clone().with_precision(4), Exact(_)));
184    /// assert!(matches!(a.clone().with_precision(3), Inexact(_, _)));
185    /// # Ok::<(), ParseError>(())
186    /// ```
187    #[inline]
188    pub fn digits(&self) -> usize {
189        self.repr.digits()
190    }
191
192    /// Get the context associated with the float number
193    #[inline]
194    pub const fn context(&self) -> Context<R> {
195        self.context
196    }
197    /// Get a reference to the underlying numeric representation
198    #[inline]
199    pub const fn repr(&self) -> &Repr<B> {
200        &self.repr
201    }
202    /// Get the underlying numeric representation
203    ///
204    /// # Examples
205    ///
206    /// ```
207    /// # use dashu_float::DBig;
208    /// use dashu_float::Repr;
209    ///
210    /// let a = DBig::ONE;
211    /// assert_eq!(a.into_repr(), Repr::<10>::one());
212    /// ```
213    #[inline]
214    pub fn into_repr(self) -> Repr<B> {
215        self.repr
216    }
217
218    /// Convert raw parts (significand, exponent) into a float number.
219    ///
220    /// The precision will be inferred from significand (the lowest k such that `significand <= base^k`)
221    ///
222    /// # Examples
223    ///
224    /// ```
225    /// # use dashu_base::ParseError;
226    /// # use dashu_float::DBig;
227    /// use core::str::FromStr;
228    /// let a = DBig::from_parts((-1234).into(), -2);
229    /// assert_eq!(a, DBig::from_str("-12.34")?);
230    /// assert_eq!(a.precision(), 4); // 1234 has 4 (decimal) digits
231    /// # Ok::<(), ParseError>(())
232    /// ```
233    #[inline]
234    pub fn from_parts(significand: IBig, exponent: isize) -> Self {
235        let precision = digit_len::<B>(&significand).max(1); // set precision to 1 if signficand is zero
236        let repr = Repr::new(significand, exponent);
237        let context = Context::new(precision);
238        Self::new(repr, context)
239    }
240
241    /// Convert raw parts (significand, exponent) into a float number in a `const` context.
242    ///
243    /// It requires that the significand fits in a [DoubleWord].
244    ///
245    /// The precision will be inferred from significand (the lowest k such that `significand <= base^k`).
246    /// If the `min_precision` is provided, then the higher one from the given and inferred precision
247    /// will be used as the final precision.
248    ///
249    /// # Examples
250    ///
251    /// ```
252    /// # use dashu_base::ParseError;
253    /// # use dashu_float::DBig;
254    /// use core::str::FromStr;
255    /// use dashu_base::Sign;
256    ///
257    /// const A: DBig = DBig::from_parts_const(Sign::Negative, 1234, -2, None);
258    /// assert_eq!(A, DBig::from_str("-12.34")?);
259    /// assert_eq!(A.precision(), 4); // 1234 has 4 (decimal) digits
260    ///
261    /// const B: DBig = DBig::from_parts_const(Sign::Negative, 1234, -2, Some(5));
262    /// assert_eq!(B.precision(), 5); // overrided by the argument
263    /// # Ok::<(), ParseError>(())
264    /// ```
265    #[inline]
266    pub const fn from_parts_const(
267        sign: Sign,
268        mut significand: DoubleWord,
269        mut exponent: isize,
270        min_precision: Option<usize>,
271    ) -> Self {
272        if significand == 0 {
273            return Self::ZERO;
274        }
275
276        let mut digits = 0;
277
278        // normalize
279        if B.is_power_of_two() {
280            let base_bits = B.trailing_zeros();
281            let shift = significand.trailing_zeros() / base_bits;
282            significand >>= shift * base_bits;
283            exponent += shift as isize;
284            digits = ((DoubleWord::BITS - significand.leading_zeros() + base_bits - 1) / base_bits)
285                as usize;
286        } else {
287            let mut pow: DoubleWord = 1;
288            while significand % (B as DoubleWord) == 0 {
289                significand /= B as DoubleWord;
290                exponent += 1;
291            }
292            while let Some(next) = pow.checked_mul(B as DoubleWord) {
293                digits += 1;
294                if next > significand {
295                    break;
296                }
297                pow = next;
298            }
299        }
300
301        let repr = Repr {
302            significand: IBig::from_parts_const(sign, significand),
303            exponent,
304        };
305        let precision = match min_precision {
306            Some(prec) => {
307                if prec > digits {
308                    prec
309                } else {
310                    digits
311                }
312            }
313            None => digits,
314        };
315        Self::new(repr, Context::new(precision))
316    }
317
318    /// Return the value of the least significant digit of the float number x,
319    /// such that `x + ulp` is the first float number greater than x (given the precision from the context).
320    ///
321    /// # Examples
322    ///
323    /// ```
324    /// # use core::str::FromStr;
325    /// # use dashu_base::ParseError;
326    /// # use dashu_float::DBig;
327    /// assert_eq!(DBig::from_str("1.23")?.ulp(), DBig::from_str("0.01")?);
328    /// assert_eq!(DBig::from_str("01.23")?.ulp(), DBig::from_str("0.001")?);
329    /// # Ok::<(), ParseError>(())
330    /// ```
331    ///
332    /// # Panics
333    /// Panics if the precision of the number is 0 (unlimited).
334    ///
335    #[inline]
336    pub fn ulp(&self) -> Self {
337        if self.context.precision == 0 {
338            panic_unlimited_precision();
339        }
340        if self.repr.is_infinite() {
341            return self.clone();
342        }
343
344        let repr = Repr {
345            significand: IBig::ONE,
346            exponent: self.repr.exponent + self.repr.digits() as isize
347                - self.context.precision as isize,
348        };
349        Self::new(repr, self.context)
350    }
351
352    /// Similar to [FBig::ulp], but use approximated digits. It's guaranteed to be smaller than ulp(), for internal use only.
353    #[inline]
354    pub(crate) fn sub_ulp(&self) -> Self {
355        debug_assert!(self.context.precision != 0);
356        debug_assert!(self.repr.is_finite());
357
358        let repr = Repr {
359            significand: IBig::ONE,
360            exponent: self.repr.exponent + self.repr.digits_lb() as isize
361                - self.context.precision as isize
362                - 1,
363        };
364        Self::new(repr, self.context)
365    }
366}
367
368// This custom implementation is necessary due to https://github.com/rust-lang/rust/issues/98374
369impl<R: Round, const B: Word> Clone for FBig<R, B> {
370    #[inline]
371    fn clone(&self) -> Self {
372        Self {
373            repr: self.repr.clone(),
374            context: self.context,
375        }
376    }
377
378    #[inline]
379    fn clone_from(&mut self, source: &Self) {
380        self.repr.clone_from(&source.repr);
381        self.context = source.context;
382    }
383}
384
385impl<R: Round, const B: Word> Default for FBig<R, B> {
386    /// Default value: 0.
387    #[inline]
388    fn default() -> Self {
389        Self::ZERO
390    }
391}