Skip to main content

dashu_float/
parse.rs

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