Skip to main content

dashu_float/
parse.rs

1use crate::{
2    fbig::FBig,
3    repr::{Context, Repr, Word},
4    round::Round,
5};
6use core::{num::IntErrorKind, str::FromStr};
7use dashu_base::{ParseError, Sign};
8use dashu_int::{
9    fmt::{MAX_RADIX, MIN_RADIX},
10    UBig,
11};
12
13impl<const B: Word> Repr<B> {
14    /// Convert a string in the native base (i.e. radix `B`) to [Repr].
15    ///
16    /// Upon success, this method returns an [Repr] and the number of digits (in radix `B`)
17    /// contained in the string.
18    ///
19    /// This method is the underlying implementation of the [`FromStr`] impl on
20    /// [`FBig`]; see those docs for the accepted formats.
21    pub(crate) fn from_str_native(mut src: &str) -> Result<(Self, usize), ParseError> {
22        assert!(MIN_RADIX as Word <= B && B <= MAX_RADIX as Word);
23
24        // B is guaranteed to be in 2..=36 by the assert above; the cast to u32
25        // is needed because `from_str_radix` takes a u32 radix. On 32-bit Word
26        // targets the cast is a no-op.
27        #[allow(clippy::unnecessary_cast)]
28        let radix: u32 = B as u32;
29
30        // parse and remove the sign
31        let sign = match src.strip_prefix('-') {
32            Some(s) => {
33                src = s;
34                Sign::Negative
35            }
36            None => {
37                src = src.strip_prefix('+').unwrap_or(src);
38                Sign::Positive
39            }
40        };
41
42        // determine the position of scale markers
43        let has_prefix = src.starts_with("0x") || src.starts_with("0X");
44        let scale_pos = match B {
45            10 => src.rfind(['e', 'E', '@']),
46            2 => {
47                if has_prefix {
48                    src.rfind(['p', 'P', '@'])
49                } else {
50                    src.rfind(['b', 'B', '@'])
51                }
52            }
53            8 => src.rfind(['o', 'O', '@']),
54            16 => src.rfind(['h', 'H', '@']),
55            _ => src.rfind('@'),
56        };
57
58        // parse scale and remove the scale part from the str
59        let (scale, pmarker) = if let Some(pos) = scale_pos {
60            let value = match src[pos + 1..].parse::<isize>() {
61                Err(e) => match e.kind() {
62                    IntErrorKind::Empty => return Err(ParseError::NoDigits),
63                    _ => return Err(ParseError::InvalidDigit),
64                },
65                Ok(v) => v,
66            };
67            let use_p = if B == 2 {
68                src.as_bytes().get(pos) == Some(&b'p') || src.as_bytes().get(pos) == Some(&b'P')
69            } else {
70                false
71            };
72            src = &src[..pos];
73            (value, use_p)
74        } else {
75            (0, false)
76        };
77
78        // parse the body of the float number
79        let mut exponent = scale;
80        let ndigits;
81        let significand = if let Some(dot) = src.find('.') {
82            // check whether both integral part and fractional part are empty
83            if src.len() == 1 {
84                return Err(ParseError::NoDigits);
85            }
86
87            // parse integral part
88            let (int, int_digits, base) = if dot != 0 {
89                let int_str = &src[..dot];
90                if B == 2 && has_prefix {
91                    // only base 2 float is allowed using prefix
92                    let int_str = &int_str[2..];
93                    let digits = 4 * (int_str.len() - int_str.matches('_').count());
94                    if int_str.is_empty() {
95                        (UBig::ZERO, digits, 16)
96                    } else {
97                        (UBig::from_str_radix(int_str, 16)?, digits, 16)
98                    }
99                } else if B == 2 && pmarker && !has_prefix {
100                    return Err(ParseError::UnsupportedRadix);
101                } else {
102                    let digits = int_str.len() - int_str.matches('_').count();
103                    (UBig::from_str_radix(&src[..dot], radix)?, digits, radix)
104                }
105            } else {
106                if pmarker {
107                    // prefix is required for using `p` as scale marker
108                    return Err(ParseError::UnsupportedRadix);
109                }
110                (UBig::ZERO, 0, radix)
111            };
112
113            // parse fractional part
114            src = &src[dot + 1..];
115            let (fract, fract_digits) = if !src.is_empty() {
116                let mut digits = src.len() - src.matches('_').count();
117                if B == 2 && base == 16 {
118                    digits *= 4;
119                }
120                (UBig::from_str_radix(src, base)?, digits)
121            } else {
122                (UBig::ZERO, 0)
123            };
124            ndigits = int_digits + fract_digits;
125
126            if fract.is_zero() {
127                int
128            } else {
129                exponent -= fract_digits as isize;
130                int * UBig::from_word(B).pow(fract_digits) + fract
131            }
132        } else {
133            let has_prefix = src.starts_with("0x") || src.starts_with("0X");
134            if B == 2 && has_prefix {
135                src = &src[2..];
136                ndigits = 4 * (src.len() - src.matches('_').count());
137                UBig::from_str_radix(src, 16)?
138            } else if B == 2 && pmarker && !has_prefix {
139                return Err(ParseError::UnsupportedRadix);
140            } else {
141                ndigits = src.len() - src.matches('_').count();
142                UBig::from_str_radix(src, radix)?
143            }
144        };
145
146        let repr = Repr::new(sign * significand, exponent);
147        Ok((repr, ndigits))
148    }
149}
150
151impl<R: Round, const B: Word> FromStr for FBig<R, B> {
152    type Err = ParseError;
153
154    /// Convert a string in the native base (i.e. radix `B`) to [`FBig`].
155    ///
156    /// If parsing succeeds, the result is **losslessly** parsed from the input
157    /// string, and its precision equals the number of significant digits in the
158    /// input.
159    ///
160    /// **Note**: Infinites are **intentionally not supported** by this function.
161    ///
162    /// # Format
163    ///
164    /// The valid representations include
165    /// 1. `aaa` or `aaa.`
166    ///     * `aaa` is represented in native base `B` without base prefixes.
167    /// 1. `aaa.bbb` = `aaabbb / base ^ len(bbb)`
168    ///     * `aaa` and `bbb` are represented in native base `B` without base prefixes.
169    ///     * `len(bbb)` represents the number of digits in `bbb`, e.g `len(bbb)` is 3. (Same below)
170    /// 1. `aaa.bbb@cc` = `aaabbb * base ^ (cc - len(bbb))`
171    ///     * `aaa` and `bbb` are represented in native base `B`
172    ///     * This is consistent with the representation used by [GNU GMP](https://gmplib.org/manual/I_002fO-of-Floats).
173    /// 1. `aaa.bbbEcc` = `aaabbb * 10 ^ (cc - len(bbb))`
174    ///     * `E` could be lower case, base `B` must be 10
175    ///     * `aaa` and `bbb` are all represented in decimal
176    /// 1. `0xaaa` or `0xaaa`
177    /// 1. `0xaaa.bbb` = `0xaaabbb / 16 ^ len(bbb)`
178    /// 1. `0xaaa.bbbPcc` = `0xaaabbb / 16 ^ len(bbb) * 2 ^ cc`
179    ///     * `P` could be lower case, base `B` must be 2 (not 16!)
180    ///     * `aaa` and `bbb` are represented in hexadecimal
181    ///     * This is consistent with the [C++ hexadecimal literals](https://en.cppreference.com/w/cpp/language/floating_literal).
182    /// 1. `aaa.bbbBcc` = `aaabbb * 2 ^ (cc - len(bbb))`
183    /// 1. `aaa.bbbOcc` = `aaabbb * 8 ^ (cc - len(bbb))`
184    /// 1. `aaa.bbbHcc` = `aaabbb * 16 ^ (cc - len(bbb))`
185    ///     * `B`/`O`/`H` could be lower case, and base `B` must be consistent with the marker.
186    ///     * `aaa` and `bbb` are represented in binary/octal/hexadecimal correspondingly without prefix.
187    ///     * This is consistent with some scientific notations described in [Wikipedia](https://en.wikipedia.org/wiki/Scientific_notation#Other_bases).
188    ///
189    /// Digits 10-35 are represented by `a-z` or `A-Z`.
190    ///
191    /// Literal `aaa` and `cc` above can be signed, but `bbb` must be unsigned.
192    /// All `cc` are represented in decimal. Either `aaa` or `bbb` can be omitted
193    /// when its value is zero, but they are not allowed to be omitted at the same time.
194    ///
195    /// # Precision
196    ///
197    /// The precision of the parsed number is determined by the number of digits that are presented
198    /// in the input string. For example, the numbers parsed from `12.34` or `1.234e-1` will have a
199    /// precision of 4, while the ones parsed from `12.34000` or `00012.34` will have a precision of 7.
200    ///
201    /// # Panics
202    ///
203    /// Panics if the base `B` is not between [MIN_RADIX] and [MAX_RADIX] inclusive.
204    ///
205    /// # Examples
206    ///
207    /// ```
208    /// # use dashu_base::ParseError;
209    /// # use dashu_float::DBig;
210    /// # use core::str::FromStr;
211    /// let a = DBig::from_str("-1.23400e-3")?;
212    /// let b = DBig::from_str("-123.4@-05")?;
213    /// assert_eq!(a, b);
214    /// assert_eq!(a.precision(), 6);
215    /// assert_eq!(b.precision(), 4);
216    ///
217    /// assert!(DBig::from_str("-0x1.234p-3").is_err());
218    /// assert!(DBig::from_str("-1.234H-3").is_err());
219    /// # Ok::<(), ParseError>(())
220    /// ```
221    #[inline]
222    fn from_str(s: &str) -> Result<Self, ParseError> {
223        let (repr, ndigits) = Repr::from_str_native(s)?;
224        Ok(Self {
225            repr,
226            context: Context::new(ndigits),
227        })
228    }
229}