WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit efd5b0a

Browse files
author
Dmitriy Shestavin
committed
feat: Implemented parsing from string with scientific notation.
1 parent 8117b77 commit efd5b0a

File tree

3 files changed

+1011
-5
lines changed

3 files changed

+1011
-5
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
<!-- next-header -->
88

99
## [Unreleased] - ReleaseDate
10+
### Added
11+
- Implemented parsing from string with scientific notation.
1012

1113
## [0.9.2] - 2023-03-02
1214
### Added

src/string.rs

Lines changed: 182 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ macro_rules! impl_for {
1717
///
1818
/// Use `from_str_exact` to parse without rounding.
1919
fn from_str(str: &str) -> Result<Self, Self::Err> {
20-
Self::parse_str::<false>(str)
20+
Self::parse_str_with_scientific::<false>(str)
2121
}
2222
}
2323

@@ -27,10 +27,12 @@ macro_rules! impl_for {
2727
///
2828
/// Use the `FromStr` instance to parse with rounding.
2929
pub fn from_str_exact(str: &str) -> Result<Self, ConvertError> {
30-
Self::parse_str::<true>(str)
30+
Self::parse_str_with_scientific::<true>(str)
3131
}
3232

33-
fn parse_str<const EXACT: bool>(str: &str) -> Result<Self, ConvertError> {
33+
fn parse_str_without_scientific<const EXACT: bool>(
34+
str: &str,
35+
) -> Result<Self, ConvertError> {
3436
let str = str.trim();
3537

3638
let (integral_str, mut fractional_str) = if let Some(parts) = str.split_once('.') {
@@ -97,6 +99,183 @@ macro_rules! impl_for {
9799
.map(Self::from_bits)
98100
.ok_or_else(|| ConvertError::new("too big number"))
99101
}
102+
103+
fn parse_str_with_scientific<const EXACT: bool>(
104+
str: &str,
105+
) -> Result<Self, ConvertError> {
106+
let str = str.trim();
107+
108+
let (integral_and_fractional_str, exponent_str) = if let Some(exponent_char) =
109+
str.chars().find(|c| *c == 'e' || *c == 'E')
110+
{
111+
if let Some(parts) = str.split_once(exponent_char) {
112+
parts
113+
} else {
114+
// This error should never happen because `exponent_char` already found.
115+
return Err(ConvertError::new("unable to split string by exponent char"));
116+
}
117+
} else {
118+
return Self::parse_str_without_scientific::<EXACT>(str);
119+
};
120+
121+
let mut exponent: i32 = exponent_str
122+
.parse()
123+
.map_err(|_| ConvertError::new("can't parse exponent"))?;
124+
125+
let (mut integral_str, mut fractional_str) =
126+
if let Some((integral_str, fractional_str)) =
127+
integral_and_fractional_str.split_once('.')
128+
{
129+
(integral_str.to_owned(), fractional_str.to_owned())
130+
} else {
131+
(integral_and_fractional_str.to_owned(), "".to_owned())
132+
};
133+
134+
if !integral_str.is_empty() {
135+
let mut chars = integral_str.chars();
136+
let first_char = chars.next().expect("unreachable");
137+
if first_char == '+' || first_char == '-' {
138+
integral_str = chars.as_str().to_owned();
139+
}
140+
141+
integral_str = integral_str.trim_start_matches('0').to_owned();
142+
}
143+
144+
let exponent_abs = exponent.abs() as usize;
145+
// Main idea here is to keep one of parts empty or zero exponent.
146+
if exponent >= 0 {
147+
if exponent_abs >= fractional_str.len() {
148+
integral_str.push_str(fractional_str.as_str());
149+
exponent = exponent
150+
.checked_sub(fractional_str.len() as i32) // `as i32`` is safe because `exponent.abs() as usize >= fractional_str.len()`
151+
.ok_or(ConvertError::new("too small exponent"))?;
152+
fractional_str = "".to_owned();
153+
} else {
154+
integral_str.push_str(&fractional_str[0..exponent_abs]);
155+
fractional_str = fractional_str[exponent_abs..].to_owned();
156+
exponent = 0;
157+
}
158+
} else {
159+
if exponent_abs >= integral_str.len() {
160+
fractional_str.insert_str(0, integral_str.as_str());
161+
exponent = exponent
162+
.checked_add(integral_str.len() as i32) // `as i32`` is safe because `exponent.abs() as usize >= integral_str.len()`
163+
.ok_or(ConvertError::new("too large exponent"))?;
164+
integral_str = "".to_owned();
165+
} else {
166+
fractional_str.insert_str(
167+
0,
168+
&integral_str[integral_str.len() - exponent_abs..integral_str.len()],
169+
);
170+
integral_str = integral_str[..integral_str.len() - exponent_abs].to_owned();
171+
exponent = 0;
172+
}
173+
}
174+
175+
if !integral_str.is_empty() {
176+
integral_str = integral_str.trim_start_matches('0').to_owned();
177+
}
178+
179+
// `exponent_abs` must be reevaluated
180+
let exponent_abs = exponent.abs() as usize;
181+
182+
debug_assert!(
183+
exponent == 0 || fractional_str.is_empty() || integral_str.is_empty()
184+
);
185+
let integral: $layout = if integral_str.is_empty() {
186+
debug_assert!((exponent == 0 || fractional_str.is_empty()) && exponent <= 0);
187+
0
188+
} else {
189+
// If at this point `integral_str` part can't be represent as `$layout` then
190+
// it obviously can't be represented as `$layout` after multiplication by `exponent`
191+
// because `exponent` here is not less than zero.
192+
debug_assert!((exponent == 0 || fractional_str.is_empty()) && exponent >= 0);
193+
integral_str
194+
.parse()
195+
.map_err(|_| ConvertError::new("can't parse integral part"))?
196+
};
197+
198+
if EXACT {
199+
// if `fractional_str` contains trailing zeroes this error will be misleading
200+
fractional_str = fractional_str.trim_end_matches('0').to_owned();
201+
if fractional_str.len() > (Self::PRECISION.abs() + exponent) as usize {
202+
return Err(ConvertError::new("requested precision is too high"));
203+
}
204+
}
205+
206+
let signum = if str.as_bytes()[0] == b'-' { -1 } else { 1 };
207+
let last_idx = Self::PRECISION + exponent;
208+
let last_idx_abs = last_idx.abs() as usize;
209+
let round = if !EXACT && last_idx >= 0 && last_idx_abs < fractional_str.len() {
210+
let extra = fractional_str.as_bytes()[last_idx_abs];
211+
fractional_str = fractional_str[..last_idx_abs].to_owned();
212+
Some(signum).filter(|_| extra >= b'5')
213+
} else {
214+
None
215+
};
216+
217+
let ten: $layout = 10;
218+
let fractional_multiplier = ten.pow(
219+
(fractional_str.len() + exponent_abs)
220+
.try_into()
221+
.map_err(|_| ConvertError::new("too big fractional_str"))?,
222+
);
223+
224+
if EXACT && fractional_multiplier > Self::COEF {
225+
return Err(ConvertError::new("requested precision is too high"));
226+
}
227+
228+
debug_assert!(fractional_multiplier <= Self::COEF);
229+
230+
let fractional: Option<$layout> = if !fractional_str.is_empty() {
231+
Some(
232+
fractional_str
233+
.parse()
234+
.map_err(|_| ConvertError::new("can't parse fractional part"))?,
235+
)
236+
} else {
237+
None
238+
};
239+
240+
let integral_multiplier = ten.pow(
241+
(Self::PRECISION + exponent)
242+
.try_into()
243+
.map_err(|_| ConvertError::new("too big exponent"))?,
244+
);
245+
let mut final_integral = integral
246+
.checked_mul(integral_multiplier)
247+
.ok_or(ConvertError::new("too big integral"))?;
248+
249+
if signum < 0 {
250+
final_integral = -final_integral;
251+
}
252+
253+
let mut final_fractional = fractional
254+
.map(|fractional| signum * Self::COEF / fractional_multiplier * fractional);
255+
256+
if let Some(round) = round {
257+
debug_assert!(!EXACT);
258+
if let Some(final_fractional_inner) = final_fractional.as_mut() {
259+
final_fractional = Some(
260+
final_fractional_inner
261+
.checked_add(round)
262+
.ok_or(ConvertError::new("requested precision is too high"))?,
263+
);
264+
} else {
265+
final_integral = final_integral
266+
.checked_add(round)
267+
.ok_or(ConvertError::new("too big integral"))?;
268+
}
269+
}
270+
271+
if let Some(&mut final_fractional) = final_fractional.as_mut() {
272+
final_integral = final_integral
273+
.checked_add(final_fractional)
274+
.ok_or(ConvertError::new("too big number"))?;
275+
}
276+
277+
Ok(Self::from_bits(final_integral))
278+
}
100279
}
101280

102281
impl<P: Precision> Stringify for FixedPoint<$layout, P> {

0 commit comments

Comments
 (0)