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/// The float number consists of a [Repr] and a [Context]. The [Repr] instance determines
13/// the value of the number, and the [Context] contains runtime information (such as precision
14/// limit, rounding mode, etc.)
15///
16/// For how the number is represented, see [Repr], for how the precision limit and rounding
17/// mode is applied, see [Context].
18///
19/// The arithmetic operations on [FBig] follows the behavior of its associated context.
20/// If a different precision limit and/or rounding mode is required, or the rounding
21/// information has to be preserved, use the methods of the [Context] type.
22///
23/// # Generic Parameters
24///
25/// The const generic parameters will be abbreviated as `BASE` -> `B`, `RoundingMode` -> `R`.
26/// THe `BASE` must be in range \[2, isize::MAX\], and the `RoundingMode` can be chosen from
27/// the [mode] module.
28///
29/// With the default generic parameters, the floating number is of base 2 rounded towards zero.
30/// This is the most efficient format for operations. To represent a decimal number, the alias
31/// [DBig][crate::DBig] is provided, which is base 10 rounded to the nearest.
32///
33/// # Parsing and printing
34///
35/// To create a [FBig] instance, there are four ways:
36/// 1. Use predifined constants (e.g. [FBig::ZERO], [FBig::ONE], [FBig::NEG_INFINITY]).
37/// 1. Use the literal macro `fbig!` or `dbig!` defined in the [`dashu-macro`](https://docs.rs/dashu-macros/latest/dashu_macros/) crate.
38/// 1. Construct from the significand and exponent using [from_parts()][FBig::from_parts] or [from_parts_const()][FBig::from_parts_const].
39/// 1. Parse from a string.
40///
41/// Conversion from and to [str] is limited to native radix (i.e. base). To print or parse
42/// with different radix, please use [to_binary()][FBig::to_binary], [to_decimal()][FBig::to_decimal]
43/// or [with_base()][FBig::with_base], [with_base_and_precision()][FBig::with_base_and_precision] to convert.
44///
45/// For printing, currently only the [Display][core::fmt::Display] and [Debug][core::fmt::Debug] are supported.
46/// Other formatting traits will be supported in future.
47///
48/// ```
49/// # use dashu_base::ParseError;
50/// # use dashu_float::DBig;
51/// use core::str::FromStr;
52///
53/// // parsing
54/// let a = DBig::from_parts(123456789.into(), -5);
55/// let b = DBig::from_str("1234.56789")?;
56/// let c = DBig::from_str("1.23456789e3")?;
57/// assert_eq!(a, b);
58/// assert_eq!(b, c);
59///
60/// // printing
61/// assert_eq!(format!("{}", DBig::from_str("12.34")?), "12.34");
62/// let x = DBig::from_str("10.01")?
63/// .with_precision(0) // use unlimited precision
64/// .value();
65/// if dashu_int::Word::BITS == 64 {
66/// // number of digits to display depends on the word size
67/// assert_eq!(
68/// format!("{:?}", x.powi(100.into())),
69/// "1105115697720767968..1441386704950100001 * 10 ^ -200 (prec: 0)"
70/// );
71/// }
72/// # Ok::<(), ParseError>(())
73/// ```
74///
75/// For detailed information of parsing, refer to the [from_str_native()][FBig::from_str_native] method.
76///
77/// # Restriction on binary operators
78///
79/// Binary operators on [FBig] instances are restricted to the same base and same rounding mode. This is
80/// designed to make sure that no hidden conversion is performed during the operators. However, for equality
81/// test and comparsion, two [FBig] instances can have different rounding modes (but not different bases),
82/// because rounding will never happends during comparison.
83///
84/// The infinities are converted as it is, and the subnormals are converted using its actual values.
85///
86/// # IEEE 754 behavior compliance
87///
88/// The representation of the floating point number doesn't follows the IEEE 754 standard, as it's not
89/// designed for arbitrary precision numbers. The key differences include:
90/// * [FBig] doesn't support NaN values. In places where IEEE 754 operations generate NaNs, `FBig` will panic.
91/// * [FBig] doesn't have subnormal values.
92/// * [FBig] doesn't have negative zeros¹. There is only on zero value ([FBig::ZERO]).
93/// * Division by zero and logarithm on zero panic instead of returning infinities.
94/// * [FBig] operations will panic if the result overflows or underflows¹.
95/// * [FBig] does support infinities, but currently infinities are not allowed to be operated with, except for
96/// equality test and comparison¹.
97///
98/// ¹ These behaviors are subject to changes in the future.
99///
100/// # Convert from/to `f32`/`f64`
101///
102/// Converting from [f32]/[f64] to [FBig] is only defined for base 2 [FBig] (using [TryFrom][core::convert::TryFrom])
103/// to ensure the conversion is lossless. Since [FBig] doesn't support `NAN`s, converting `f32::NAN` or `f64::NAN` will
104/// return [Err].
105///
106/// Converting to [f32]/[f64] (using [to_f32()][FBig::to_f32] and [to_f64()][FBig::to_f64]) can be lossy, and the rounding
107/// direction is contained in the result of these two methods. To use the default IEEE 754 rounding mode (rounding to
108/// nearest), the [Repr::to_f32] and [Repr::to_f64] methods can be used for convenience.
109///
110/// # Convert from/to `UBig`/`IBig`
111///
112/// Converting from `UBig` and `IBig` is trivial and lossless through [From]. However, the reverse direction can be lossy.
113///
114/// The [TryFrom] trait and [to_int()][FBig::to_int] method are the two supported ways to convert from [FBig] to [IBig].
115/// To convert to [UBig][dashu_int::UBig], please first convert to [IBig]. When converting to [IBig], [TryFrom] returns
116/// [Ok] only when the floating point number is not infinite and doesn't have fractional part. To convert with rounding,
117/// use [to_int()][FBig::to_int] instead.
118pub struct FBig<RoundingMode: Round = mode::Zero, const BASE: Word = 2> {
119 pub(crate) repr: Repr<BASE>,
120 pub(crate) context: Context<RoundingMode>,
121}
122
123impl<R: Round, const B: Word> FBig<R, B> {
124 /// Create a [FBig] instance from raw parts, internal use only
125 #[inline]
126 pub(crate) const fn new(repr: Repr<B>, context: Context<R>) -> Self {
127 Self { repr, context }
128 }
129
130 /// Create a [FBig] instance from [Repr] and [Context].
131 ///
132 /// This method should not be used in most cases. It's designed to be used when
133 /// you hold a [Repr] instance and want to create an [FBig] from that.
134 ///
135 /// # Examples
136 ///
137 /// ```
138 /// # use dashu_float::DBig;
139 /// use dashu_float::{Repr, Context};
140 ///
141 /// assert_eq!(DBig::from_repr(Repr::one(), Context::new(1)), DBig::ONE);
142 /// assert_eq!(DBig::from_repr(Repr::infinity(), Context::new(1)), DBig::INFINITY);
143 /// ```
144 ///
145 /// # Panics
146 ///
147 /// Panics if the [Repr] has more digits than the precision limit specified in the context.
148 /// Note that this condition is not checked in release builds.
149 #[inline]
150 pub fn from_repr(repr: Repr<B>, context: Context<R>) -> Self {
151 debug_assert!(
152 repr.is_infinite() || !context.is_limited() || repr.digits() <= context.precision
153 );
154 Self { repr, context }
155 }
156
157 /// Create a [FBig] instance from [Repr]. Due to the limitation of const operations,
158 /// the precision of the float is set to unlimited.
159 ///
160 /// # Examples
161 ///
162 /// ```
163 /// # use dashu_float::DBig;
164 /// use dashu_float::{Repr, Context};
165 ///
166 /// assert_eq!(DBig::from_repr_const(Repr::one()), DBig::ONE);
167 /// assert_eq!(DBig::from_repr_const(Repr::infinity()), DBig::INFINITY);
168 /// ```
169 #[inline]
170 pub const fn from_repr_const(repr: Repr<B>) -> Self {
171 Self {
172 repr,
173 context: Context::new(0),
174 }
175 }
176
177 /// [FBig] with value 0 and unlimited precision
178 ///
179 /// To test if the float number is zero, use `self.repr().is_zero()`.
180 pub const ZERO: Self = Self::new(Repr::zero(), Context::new(0));
181
182 /// [FBig] with value 1 and unlimited precision
183 ///
184 /// To test if the float number is one, use `self.repr().is_one()`.
185 pub const ONE: Self = Self::new(Repr::one(), Context::new(0));
186
187 /// [FBig] with value -1 and unlimited precision
188 pub const NEG_ONE: Self = Self::new(Repr::neg_one(), Context::new(0));
189
190 /// [FBig] instance representing the positive infinity (+∞)
191 ///
192 /// To test if the float number is infinite, use `self.repr().infinite()`.
193 pub const INFINITY: Self = Self::new(Repr::infinity(), Context::new(0));
194
195 /// [FBig] instance representing the negative infinity (-∞)
196 ///
197 /// To test if the float number is infinite, use `self.repr().infinite()`.
198 pub const NEG_INFINITY: Self = Self::new(Repr::neg_infinity(), Context::new(0));
199
200 /// Get the maximum precision set for the float number.
201 ///
202 /// It's equivalent to `self.context().precision()`.
203 ///
204 /// # Examples
205 ///
206 /// ```
207 /// # use core::str::FromStr;
208 /// # use dashu_base::ParseError;
209 /// # use dashu_float::DBig;
210 /// # use dashu_int::IBig;
211 /// use dashu_float::Repr;
212 ///
213 /// let a = DBig::from_str("1.234")?;
214 /// assert!(a.repr().significand() <= &IBig::from(10).pow(a.precision()));
215 /// # Ok::<(), ParseError>(())
216 /// ```
217 #[inline]
218 pub const fn precision(&self) -> usize {
219 self.context.precision
220 }
221
222 /// Get the number of the significant digits in the float number
223 ///
224 /// It's equivalent to `self.repr().digits()`.
225 ///
226 /// This value is also the actual precision needed for the float number. Shrink to this
227 /// value using [with_precision()][FBig::with_precision] will not cause loss of float precision.
228 ///
229 /// # Examples
230 ///
231 /// ```
232 /// # use core::str::FromStr;
233 /// # use dashu_base::ParseError;
234 /// # use dashu_float::DBig;
235 /// use dashu_base::Approximation::*;
236 ///
237 /// let a = DBig::from_str("-1.234e-3")?;
238 /// assert_eq!(a.digits(), 4);
239 /// assert!(matches!(a.clone().with_precision(4), Exact(_)));
240 /// assert!(matches!(a.clone().with_precision(3), Inexact(_, _)));
241 /// # Ok::<(), ParseError>(())
242 /// ```
243 #[inline]
244 pub fn digits(&self) -> usize {
245 self.repr.digits()
246 }
247
248 /// Get the context associated with the float number
249 #[inline]
250 pub const fn context(&self) -> Context<R> {
251 self.context
252 }
253 /// Get a reference to the underlying numeric representation
254 #[inline]
255 pub const fn repr(&self) -> &Repr<B> {
256 &self.repr
257 }
258 /// Get the underlying numeric representation
259 ///
260 /// # Examples
261 ///
262 /// ```
263 /// # use dashu_float::DBig;
264 /// use dashu_float::Repr;
265 ///
266 /// let a = DBig::ONE;
267 /// assert_eq!(a.into_repr(), Repr::<10>::one());
268 /// ```
269 #[inline]
270 pub fn into_repr(self) -> Repr<B> {
271 self.repr
272 }
273
274 /// Convert raw parts (significand, exponent) into a float number.
275 ///
276 /// The precision will be inferred from significand (the lowest k such that `significand <= base^k`)
277 ///
278 /// # Examples
279 ///
280 /// ```
281 /// # use dashu_base::ParseError;
282 /// # use dashu_float::DBig;
283 /// use core::str::FromStr;
284 /// let a = DBig::from_parts((-1234).into(), -2);
285 /// assert_eq!(a, DBig::from_str("-12.34")?);
286 /// assert_eq!(a.precision(), 4); // 1234 has 4 (decimal) digits
287 /// # Ok::<(), ParseError>(())
288 /// ```
289 #[inline]
290 pub fn from_parts(significand: IBig, exponent: isize) -> Self {
291 let precision = digit_len::<B>(&significand).max(1); // set precision to 1 if signficand is zero
292 let repr = Repr::new(significand, exponent);
293 let context = Context::new(precision);
294 Self::new(repr, context)
295 }
296
297 /// Convert raw parts (significand, exponent) into a float number in a `const` context.
298 ///
299 /// It requires that the significand fits in a [DoubleWord].
300 ///
301 /// The precision will be inferred from significand (the lowest k such that `significand <= base^k`).
302 /// If the `min_precision` is provided, then the higher one from the given and inferred precision
303 /// will be used as the final precision.
304 ///
305 /// # Examples
306 ///
307 /// ```
308 /// # use dashu_base::ParseError;
309 /// # use dashu_float::DBig;
310 /// use core::str::FromStr;
311 /// use dashu_base::Sign;
312 ///
313 /// const A: DBig = DBig::from_parts_const(Sign::Negative, 1234, -2, None);
314 /// assert_eq!(A, DBig::from_str("-12.34")?);
315 /// assert_eq!(A.precision(), 4); // 1234 has 4 (decimal) digits
316 ///
317 /// const B: DBig = DBig::from_parts_const(Sign::Negative, 1234, -2, Some(5));
318 /// assert_eq!(B.precision(), 5); // overrided by the argument
319 /// # Ok::<(), ParseError>(())
320 /// ```
321 #[inline]
322 pub const fn from_parts_const(
323 sign: Sign,
324 mut significand: DoubleWord,
325 mut exponent: isize,
326 min_precision: Option<usize>,
327 ) -> Self {
328 if significand == 0 {
329 return Self::ZERO;
330 }
331
332 let mut digits = 0;
333
334 // normalize
335 if B.is_power_of_two() {
336 let base_bits = B.trailing_zeros();
337 let shift = significand.trailing_zeros() / base_bits;
338 significand >>= shift * base_bits;
339 exponent += shift as isize;
340 digits = ((DoubleWord::BITS - significand.leading_zeros() + base_bits - 1) / base_bits)
341 as usize;
342 } else {
343 let mut pow: DoubleWord = 1;
344 while significand % (B as DoubleWord) == 0 {
345 significand /= B as DoubleWord;
346 exponent += 1;
347 }
348 while let Some(next) = pow.checked_mul(B as DoubleWord) {
349 digits += 1;
350 if next > significand {
351 break;
352 }
353 pow = next;
354 }
355 }
356
357 let repr = Repr {
358 significand: IBig::from_parts_const(sign, significand),
359 exponent,
360 };
361 let precision = match min_precision {
362 Some(prec) => {
363 if prec > digits {
364 prec
365 } else {
366 digits
367 }
368 }
369 None => digits,
370 };
371 Self::new(repr, Context::new(precision))
372 }
373
374 /// Return the value of the least significant digit of the float number x,
375 /// such that `x + ulp` is the first float number greater than x (given the precision from the context).
376 ///
377 /// # Examples
378 ///
379 /// ```
380 /// # use core::str::FromStr;
381 /// # use dashu_base::ParseError;
382 /// # use dashu_float::DBig;
383 /// assert_eq!(DBig::from_str("1.23")?.ulp(), DBig::from_str("0.01")?);
384 /// assert_eq!(DBig::from_str("01.23")?.ulp(), DBig::from_str("0.001")?);
385 /// # Ok::<(), ParseError>(())
386 /// ```
387 ///
388 /// # Panics
389 /// Panics if the precision of the number is 0 (unlimited).
390 ///
391 #[inline]
392 pub fn ulp(&self) -> Self {
393 if self.context.precision == 0 {
394 panic_unlimited_precision();
395 }
396 if self.repr.is_infinite() {
397 return self.clone();
398 }
399
400 let repr = Repr {
401 significand: IBig::ONE,
402 exponent: self.repr.exponent + self.repr.digits() as isize
403 - self.context.precision as isize,
404 };
405 Self::new(repr, self.context)
406 }
407
408 /// Similar to [FBig::ulp], but use approximated digits. It's guaranteed to be smaller than ulp(), for internal use only.
409 #[inline]
410 pub(crate) fn sub_ulp(&self) -> Self {
411 debug_assert!(self.context.precision != 0);
412 debug_assert!(self.repr.is_finite());
413
414 let repr = Repr {
415 significand: IBig::ONE,
416 exponent: self.repr.exponent + self.repr.digits_lb() as isize
417 - self.context.precision as isize
418 - 1,
419 };
420 Self::new(repr, self.context)
421 }
422}
423
424// This custom implementation is necessary due to https://github.com/rust-lang/rust/issues/98374
425impl<R: Round, const B: Word> Clone for FBig<R, B> {
426 #[inline]
427 fn clone(&self) -> Self {
428 Self {
429 repr: self.repr.clone(),
430 context: self.context,
431 }
432 }
433
434 #[inline]
435 fn clone_from(&mut self, source: &Self) {
436 self.repr.clone_from(&source.repr);
437 self.context = source.context;
438 }
439}
440
441impl<R: Round, const B: Word> Default for FBig<R, B> {
442 /// Default value: 0.
443 #[inline]
444 fn default() -> Self {
445 Self::ZERO
446 }
447}