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}