malachite_base/num/conversion/string/
from_sci_string.rs1use crate::num::arithmetic::traits::Parity;
10use crate::num::basic::integers::PrimitiveInt;
11use crate::num::conversion::string::from_string::digit_from_display_byte;
12use crate::num::conversion::string::options::FromSciStringOptions;
13use crate::num::conversion::traits::FromSciString;
14use crate::rounding_modes::RoundingMode::*;
15use alloc::vec::Vec;
16use core::cmp::Ordering::{self, *};
17use core::str::FromStr;
18
19#[doc(hidden)]
20#[inline]
21pub fn parse_exponent(s: &[u8]) -> Option<i64> {
22 i64::from_str(core::str::from_utf8(s).ok()?).ok()
23}
24
25#[doc(hidden)]
26pub fn validate_helper(s: &[u8], base: u8) -> Option<()> {
27 for &c in s {
28 if digit_from_display_byte(c)? >= base {
29 return None;
30 }
31 }
32 Some(())
33}
34
35#[doc(hidden)]
36pub fn is_zero_helper(s: &[u8], base: u8) -> Option<bool> {
37 let mut all_zeros = true;
38 for &c in s {
39 let d = digit_from_display_byte(c)?;
40 if d >= base {
41 return None;
42 }
43 if d != 0 {
44 all_zeros = false;
45 }
46 }
47 Some(all_zeros)
48}
49
50#[doc(hidden)]
51pub fn cmp_half_helper(s: &[u8], base: u8) -> Option<Ordering> {
52 if s.is_empty() {
53 return Some(Less);
54 }
55 let h = base >> 1;
56 let mut done = false;
57 let mut result;
58 if base.even() {
59 result = Equal;
61 let mut first = true;
62 for &c in s {
63 let d = digit_from_display_byte(c)?;
64 if d >= base {
65 return None;
66 }
67 if done {
68 continue;
69 }
70 if first {
71 let half_c = d.cmp(&h);
72 if half_c != Equal {
73 result = half_c;
74 done = true;
75 }
76 first = false;
77 } else if d != 0 {
78 result = Greater;
79 done = true;
80 }
81 }
82 } else {
83 result = Less;
85 for &c in s {
86 let d = digit_from_display_byte(c)?;
87 if done {
88 continue;
89 }
90 let half_c = d.cmp(&h);
91 if half_c != Equal {
92 result = half_c;
93 done = true;
94 }
95 }
96 }
97 Some(result)
98}
99
100fn parse_int<T: PrimitiveInt>(cs: &[u8], base: u8) -> Option<T> {
101 let mut test_neg_zero = false;
103 if T::MIN == T::ZERO
104 && let Some(&b'-') = cs.first()
105 {
106 test_neg_zero = true;
107 }
108 if test_neg_zero {
109 if cs.len() == 1 {
110 return None;
111 }
112 for &c in &cs[1..] {
113 if c != b'0' {
114 return None;
115 }
116 }
117 Some(T::ZERO)
118 } else {
119 T::from_string_base(base, core::str::from_utf8(cs).ok()?)
120 }
121}
122
123fn up_1<T: PrimitiveInt>(x: T, neg: bool) -> Option<T> {
124 if neg {
125 x.checked_sub(T::ONE)
126 } else {
127 x.checked_add(T::ONE)
128 }
129}
130
131#[doc(hidden)]
132pub fn preprocess_sci_string(s: &str, options: FromSciStringOptions) -> Option<(Vec<u8>, i64)> {
133 let mut s = s.as_bytes().to_vec();
134 let mut exponent = 0;
135 if options.base < 15 {
136 for (i, &c) in s.iter().enumerate().rev() {
137 if c == b'e' || c == b'E' {
138 if i == 0 || i == s.len() - 1 {
139 return None;
140 }
141 exponent = parse_exponent(&s[i + 1..])?;
142 s.truncate(i);
143 break;
144 }
145 }
146 } else {
147 for (i, &c) in s.iter().enumerate().rev() {
148 if c == b'+' || c == b'-' {
149 if i == 0 {
150 break;
151 }
152 if i == 1 || i == s.len() - 1 {
153 return None;
154 }
155 let exp_indicator = s[i - 1];
156 if exp_indicator != b'e' && exp_indicator != b'E' {
157 return None;
158 }
159 exponent = parse_exponent(&s[i..])?;
160 s.truncate(i - 1);
161 break;
162 }
163 }
164 }
165 let mut point_index = None;
166 for (i, &c) in s.iter().enumerate() {
167 if c == b'.' {
168 point_index = Some(i);
169 break;
170 }
171 }
172 if let Some(point_index) = point_index {
173 let len = s.len();
174 if point_index != len - 1 {
175 let next_char = s[point_index + 1];
176 if next_char == b'+' || next_char == b'-' {
177 return None;
178 }
179 exponent = exponent.checked_sub(i64::try_from(len - point_index - 1).ok()?)?;
180 s.copy_within(point_index + 1..len, point_index);
181 }
182 s.pop();
183 }
184 Some((s, exponent))
185}
186
187fn from_sci_string_with_options_primitive_int<T: PrimitiveInt>(
188 s: &str,
189 options: FromSciStringOptions,
190) -> Option<T> {
191 let (s, exponent) = preprocess_sci_string(s, options)?;
192 if exponent >= 0 {
193 let x = parse_int::<T>(&s, options.base)?;
194 x.checked_mul(T::wrapping_from(options.base).checked_pow(exponent.unsigned_abs())?)
195 } else {
196 let neg_exponent = usize::try_from(exponent.unsigned_abs()).ok()?;
197 let len = s.len();
198 if len == 0 {
199 return None;
200 }
201 let first = s[0];
202 let neg = first == b'-';
203 let sign = neg || first == b'+';
204 let rm = if neg {
205 -options.rounding_mode
206 } else {
207 options.rounding_mode
208 };
209 let sig_len = if sign { len - 1 } else { len };
210 if sig_len == 0 {
211 return None;
212 }
213 if neg_exponent > sig_len {
214 let s = if sign { &s[1..] } else { &s[..] };
215 return match rm {
216 Down | Floor | Nearest => {
217 validate_helper(s, options.base)?;
218 Some(T::ZERO)
219 }
220 Up | Ceiling => {
221 if is_zero_helper(s, options.base)? {
222 Some(T::ZERO)
223 } else {
224 up_1(T::ZERO, neg)
225 }
226 }
227 Exact => None,
228 };
229 }
230 let (before_e, after_e) = s.split_at(len - neg_exponent);
231 let x = match before_e {
232 &[] | &[b'-'] | &[b'+'] => T::ZERO,
233 before_e => parse_int(before_e, options.base)?,
234 };
235 if after_e.is_empty() {
236 return Some(x);
237 }
238 match rm {
239 Down | Floor => {
240 validate_helper(after_e, options.base)?;
241 Some(x)
242 }
243 Up | Ceiling => {
244 if is_zero_helper(after_e, options.base)? {
245 Some(x)
246 } else {
247 up_1(x, neg)
248 }
249 }
250 Exact => {
251 if is_zero_helper(after_e, options.base)? {
252 Some(x)
253 } else {
254 None
255 }
256 }
257 Nearest => match cmp_half_helper(after_e, options.base)? {
258 Less => Some(x),
259 Greater => up_1(x, neg),
260 Equal => {
261 if x.even() {
262 Some(x)
263 } else {
264 up_1(x, neg)
265 }
266 }
267 },
268 }
269 }
270}
271
272macro_rules! impl_from_sci_string {
273 ($t:ident) => {
274 impl FromSciString for $t {
275 #[inline]
311 fn from_sci_string_with_options(s: &str, options: FromSciStringOptions) -> Option<$t> {
312 from_sci_string_with_options_primitive_int(s, options)
313 }
314 }
315 };
316}
317apply_to_primitive_ints!(impl_from_sci_string);