lexical_parse_float/number.rs
1//! Representation of a float as the significant digits and exponent.
2//!
3//! This is adapted from [fast-float-rust](https://github.com/aldanor/fast-float-rust),
4//! a port of [fast_float](https://github.com/fastfloat/fast_float) to Rust.
5
6#![doc(hidden)]
7#![allow(clippy::exhaustive_structs)] // reason = "only public for testing"
8
9use lexical_util::format::NumberFormat;
10
11use crate::float::RawFloat;
12use crate::fpu::set_precision;
13
14/// Representation of a number as the significant digits and exponent.
15///
16/// This is only used if the exponent base and the significant digit
17/// radix are the same, since we need to be able to move powers in and
18/// out of the exponent.
19#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
20pub struct Number<'a> {
21 /// The exponent of the float, scaled to the mantissa.
22 pub exponent: i64,
23 /// The significant digits of the float.
24 pub mantissa: u64,
25 /// If the float is negative.
26 pub is_negative: bool,
27 /// If the significant digits were truncated.
28 pub many_digits: bool,
29 /// The significant integer digits.
30 pub integer: &'a [u8],
31 /// The significant fraction digits.
32 pub fraction: Option<&'a [u8]>,
33}
34
35impl Number<'_> {
36 /// Detect if the float can be accurately reconstructed from native floats.
37 #[must_use]
38 #[inline(always)]
39 pub fn is_fast_path<F: RawFloat, const FORMAT: u128>(&self) -> bool {
40 let format = NumberFormat::<FORMAT> {};
41 debug_assert!(
42 format.mantissa_radix() == format.exponent_base(),
43 "fast path requires same radix"
44 );
45 F::min_exponent_fast_path(format.radix()) <= self.exponent
46 && self.exponent <= F::max_exponent_disguised_fast_path(format.radix())
47 && self.mantissa <= F::MAX_MANTISSA_FAST_PATH
48 && !self.many_digits
49 }
50
51 /// The fast path algorithm using machine-sized integers and floats.
52 ///
53 /// This is extracted into a separate function so that it can be attempted
54 /// before constructing a Decimal. This only works if both the mantissa
55 /// and the exponent can be exactly represented as a machine float,
56 /// since IEE-754 guarantees no rounding will occur.
57 ///
58 /// There is an exception: disguised fast-path cases, where we can shift
59 /// powers-of-10 from the exponent to the significant digits.
60 // `set_precision` doesn't return a unit value on x87 FPUs.
61 #[must_use]
62 #[allow(clippy::missing_inline_in_public_items)] // reason = "only public for testing"
63 #[allow(clippy::let_unit_value)] // reason = "intentional ASM drop for X87 FPUs"
64 pub fn try_fast_path<F: RawFloat, const FORMAT: u128>(&self) -> Option<F> {
65 let format = NumberFormat::<FORMAT> {};
66 debug_assert!(
67 format.mantissa_radix() == format.exponent_base(),
68 "fast path requires same radix"
69 );
70 // The fast path crucially depends on arithmetic being rounded to the correct
71 // number of bits without any intermediate rounding. On x86 (without SSE
72 // or SSE2) this requires the precision of the x87 FPU stack to be
73 // changed so that it directly rounds to 64/32 bit. The `set_precision`
74 // function takes care of setting the precision on architectures which
75 // require setting it by changing the global state (like the control word of the
76 // x87 FPU).
77 let _cw: () = set_precision::<F>();
78
79 if self.is_fast_path::<F, FORMAT>() {
80 let radix = format.radix();
81 let max_exponent = F::max_exponent_fast_path(radix);
82 let mut value = if self.exponent <= max_exponent {
83 // normal fast path
84 let value = F::as_cast(self.mantissa);
85 if self.exponent < 0 {
86 value / F::pow_fast_path((-self.exponent) as usize, radix)
87 } else {
88 value * F::pow_fast_path(self.exponent as usize, radix)
89 }
90 } else {
91 // disguised fast path
92 let shift = self.exponent - max_exponent;
93 let int_power = F::int_pow_fast_path(shift as usize, radix);
94 let mantissa = self.mantissa.checked_mul(int_power)?;
95 if mantissa > F::MAX_MANTISSA_FAST_PATH {
96 return None;
97 }
98 F::as_cast(mantissa) * F::pow_fast_path(max_exponent as usize, radix)
99 };
100 if self.is_negative {
101 value = -value;
102 }
103 Some(value)
104 } else {
105 None
106 }
107 }
108
109 /// Force a fast-path algorithm, even when it may not be accurate.
110 // `set_precision` doesn't return a unit value on x87 FPUs.
111 #[must_use]
112 #[allow(clippy::missing_inline_in_public_items)] // reason = "only public for testing"
113 #[allow(clippy::let_unit_value)] // reason = "intentional ASM drop for X87 FPUs"
114 pub fn force_fast_path<F: RawFloat, const FORMAT: u128>(&self) -> F {
115 let format = NumberFormat::<FORMAT> {};
116 debug_assert!(
117 format.mantissa_radix() == format.exponent_base(),
118 "fast path requires same radix"
119 );
120
121 let _cw = set_precision::<F>();
122
123 let radix = format.radix();
124 let mut value = F::as_cast(self.mantissa);
125 let max_exponent = F::max_exponent_fast_path(radix);
126 let mut exponent = self.exponent.abs();
127 if self.exponent < 0 {
128 while exponent > max_exponent {
129 value /= F::pow_fast_path(max_exponent as usize, radix);
130 exponent -= max_exponent;
131 }
132 value /= F::pow_fast_path(exponent as usize, radix);
133 } else {
134 while exponent > max_exponent {
135 value *= F::pow_fast_path(max_exponent as usize, radix);
136 exponent -= max_exponent;
137 }
138 value *= F::pow_fast_path(exponent as usize, radix);
139 }
140 if self.is_negative {
141 value = -value;
142 }
143 value
144 }
145}