Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_num/src/string.rs

48.5 KiB, 194 runs

created by r1870400018:641, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1use crate::{
2 float::{
3 Float32,
4 Float64,
5 },
6};
7
8use oxedyne_fe2o3_core::prelude::*;
9
10use std::{
11 fmt,
12 str::{
13 self,
14 FromStr,
15 },
16};
17
18use num_bigint::{
19 BigInt,
20 Sign,
21};
22use bigdecimal::BigDecimal;
23
24impl FromStr for Float32 {
25 type Err = Error<ErrTag>;
26
27 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
28 match s.parse::<f32>() {
29 Ok(f) => Ok(Float32(f)),
30 Err(e) => Err(err!(e,
31 "While parsing '{}' as an f32", s;
32 String, Input, Decode, Numeric)),
33 }
34 }
35}
36
37impl fmt::Display for Float32 {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 write!(f, "{}", self.0)
40 }
41}
42
43impl FromStr for Float64 {
44 type Err = Error<ErrTag>;
45
46 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
47 match s.parse::<f64>() {
48 Ok(f) => Ok(Float64(f)),
49 Err(e) => Err(err!(e,
50 "While parsing '{}' as an f64", s;
51 String, Input, Decode, Numeric)),
52 }
53 }
54}
55
56impl fmt::Display for Float64 {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 write!(f, "{}", self.0)
59 }
60}
61
62pub fn string_to_digit_vec(input: &str) -> Outcome<Vec<u8>> {
63 let mut digits = Vec::with_capacity(input.len());
64 for c in input.chars() {
65 match c.to_digit(10) {
66 Some(d) => digits.push(d as u8),
67 None => return Err(err!(
68 "Non-digit character '{}' in numeric string {:?}.", c, input;
69 Invalid, Input)),
70 }
71 }
72 Ok(digits)
73}
74
75#[derive(Clone, Debug, Eq, PartialEq)]
76pub enum NumberType {
77 SignedInt,
78 UnsignedInt,
79 FloatingPoint,
80 Hexadecimal,
81 Octal,
82 Base64,
83}
84
85#[derive(Clone, Debug, Eq, PartialEq)]
86/// An intermediate representation, simplifying subsequent conversion of the string parts to a
87/// number.
88///
89/// ```ignore
90/// -1234.567e-34
91/// +---+---+ +-+
92/// | |
93/// | +--- exponent (an integer)
94/// +----- significand made of an integer and fraction part
95/// ```
96pub struct NumberString {
97 src: String, // Source string
98 radix: u32, // The number base, e.g. 10, 2, 8, 16
99 signeg: bool, // Significand sign
100 sigint: String, // Significand integer part (absolute value)
101 digits: Vec<u8>,// Significand integer part as bytes
102 sigfrac: String, // Significand fraction part
103 exp: String, // Exponent part (absolute value)
104 expneg: bool, // Exponent sign
105 ftz: usize, // Number of trailing zeros in significand fraction part
106}
107
108impl Default for NumberString {
109 fn default() -> Self {
110 Self {
111 src: String::new(),
112 radix: 10,
113 signeg: false,
114 sigint: String::new(),
115 digits: Vec::new(),
116 sigfrac: String::new(),
117 exp: String::new(),
118 expneg: false,
119 ftz: 0,
120 }
121 }
122}
123
124impl NumberString {
125
126 const CHAR_ZERO: u8 = b"0"[0] as u8;
127 const CHAR_A_LOWER: u8 = b"a"[0] as u8;
128
129 pub fn new<S: Into<String>>(s: S) -> Outcome<Self> {
130 Self::validate(s)
131 }
132
133 /// Does the number contain a radix or decimal point?
134 pub fn has_point(&self) -> bool { self.sigfrac.len() > 0 }
135
136 pub fn source(&self) -> &str { &self.src }
137 pub fn radix(&self) -> u32 { self.radix }
138 pub fn is_negative(&self) -> bool { self.signeg }
139 pub fn abs_integer_str(&self) -> &str { &self.sigint }
140 pub fn int_string(&self) -> String {
141 if self.signeg {
142 fmt!("-{}", self.sigint)
143 } else {
144 self.sigint.clone()
145 }
146 }
147 pub fn fraction_str(&self) -> &str { &self.sigfrac }
148 pub fn exponent_str(&self) -> &str { &self.exp }
149 pub fn exponent_negative(&self) -> bool { self.expneg }
150 /// When reading digits in the fractional part of a number, we can't know how many
151 /// trailing zeros there are until we reach the end. So during validation, we simply keep a
152 /// count.
153 pub fn fraction_trailing_zeros(&self) -> usize { self.ftz }
154 pub fn is_zero(&self) -> bool {
155 self.sigint.len() == 0 && !self.has_exp()
156 }
157
158 /// Does the number contain an exponent?
159 pub fn has_exp(&self) -> bool { self.exp.len() > 0 }
160
161 fn str_to_digits(input: &str) -> Outcome<Vec<u8>> {
162 let mut digits = Vec::new();
163 let mut i: usize = 0;
164 for s in input.chars() {
165 i += 1;
166 digits.push(match s {
167 '0'..='9' => (s as u8) - Self::CHAR_ZERO,
168 'a'..='z' => (s as u8) - Self::CHAR_A_LOWER + 10,
169 _ => return Err(err!(
170 "Character '{}' at position {} is not recognised as a \
171 potential numerical digit. Use characters '0'..'9' or \
172 'a'..'z'.", s, i;
173 String, Decode, Invalid, Input)),
174 });
175 }
176 Ok(digits)
177 }
178
179 pub fn count_trailing_zeros(s: &str) -> usize {
180 let mut result = 0usize;
181 for c in s.chars().rev() {
182 if c == '0' {
183 result += 1;
184 } else {
185 break;
186 }
187 }
188 result
189 }
190
191 pub fn sign_bigint(&self) -> Sign {
192 if self.signeg {
193 Sign::Minus
194 } else {
195 Sign::Plus
196 }
197 }
198
199 /// Convert the significand integer part to a [`BigInt`]. This accomodates the common radix
200 /// values 10, 2, 8 and 16.
201 pub fn as_bigint(&self) -> Outcome<BigInt> {
202 let digits = res!(Self::str_to_digits(&self.sigint));
203 match BigInt::from_radix_be(
204 self.sign_bigint(),
205 &digits,
206 self.radix,
207 ) {
208 Some(n) => Ok(n),
209 None => Err(err!(
210 "Could not interpret the number {:?} as a BigInt.", self;
211 Conversion, Integer)),
212 }
213 }
214
215 pub fn as_bigdecimal(&self) -> Outcome<BigDecimal> {
216 let mut exp = 0i64;
217 if self.exp.len() > 0 {
218 match <i64>::from_str_radix(&self.exp, self.radix) {
219 // The exponent is held as its absolute value beside its sign.
220 Ok(n) => exp = if self.expneg { -n } else { n },
221 Err(e) => return Err(err!(e,
222 "While trying to convert exp in {:?} to i64", self;
223 String, Input, Decode, Numeric)),
224 }
225 }
226 let flen = self.sigfrac.len() - self.ftz;
227 let mut digits: Vec<u8> =
228 self.sigint
229 .as_bytes()
230 .iter()
231 .map(|x| (*x as u8) - Self::CHAR_ZERO)
232 .collect();
233 digits.append(
234 &mut self.sigfrac
235 .as_bytes()
236 .iter()
237 .take(flen)
238 .map(|x| (*x as u8) - Self::CHAR_ZERO)
239 .collect::<Vec<u8>>()
240 );
241
242 Ok(
243 BigDecimal::new(
244 match BigInt::from_radix_be(
245 self.sign_bigint(),
246 &digits,
247 self.radix,
248 ) {
249 Some(n) => n,
250 None => return Err(err!(
251 "The conversion of a NumberString with significand \
252 '{}.{}' to a digit vector {:?} using sign {:?} and radix {} \
253 resulted in a None BigInt",
254 self.sigint,
255 self.sigfrac,
256 &digits,
257 self.sign_bigint(),
258 self.radix;
259 String, Encode, Numeric)),
260 },
261 (flen as i64)-exp,
262 )
263 )
264 }
265
266 /// A commonly used error string template for the [`Self::validate`] associated function.
267 fn errmsg(
268 c: char,
269 pos: usize,
270 s: &str,
271 reason: String,
272 ) -> String {
273 fmt!(
274 "The digit '{}' was detected at position {} in the string '{}' {}",
275 c, pos, s.to_string(), reason,
276 )
277 }
278
279 /// Takes a string and validates it as an integer of the given radix, or else as a number in
280 /// decimal, or scientific notation, if the radix is 10, in a single pass. Extra
281 /// leading zeros in the significand integer and the exponent are ignored for base 10.
282 #[allow(unused_assignments)] // for exp_digit_count
283 pub fn validate<S: Into<String>>(s: S) -> Outcome<Self> {
284 let s = s.into().trim().to_lowercase();
285 let s_ref = &s;
286 // Defaults
287 let mut radix: u32 = 10; // default
288 // Initialisations
289 let mut sigint = String::new();
290 let mut sigfrac = String::new();
291 let mut exp = String::new();
292 let mut frac_trailing_zeros = 0usize;
293 let mut sigint_digit_count: usize = 0;
294 let mut exp_digit_count: usize = 0;
295
296 let mut flags = ValidationFlags::default();
297
298 let mut iter = s.chars();
299
300 if s_ref.starts_with("-") {
301 flags.signeg = true;
302 flags.sig_sign_detected = true;
303 iter.next();
304 } else if s_ref.starts_with("+") {
305 flags.signeg = false;
306 flags.sig_sign_detected = true;
307 iter.next();
308 }
309
310 let mut chars_to_skip: usize = 0;
311
312 if iter.as_str().starts_with("0x") {
313 radix = 16;
314 flags.radix_detected = true;
315 flags.sig_capture_active = true;
316 chars_to_skip = 2;
317 } else if iter.as_str().starts_with("0o") {
318 radix = 8;
319 flags.radix_detected = true;
320 flags.sig_capture_active = true;
321 chars_to_skip = 2;
322 } else if iter.as_str().starts_with("0b") {
323 radix = 2;
324 flags.radix_detected = true;
325 flags.sig_capture_active = true;
326 chars_to_skip = 2;
327 }
328
329 let mut iter2 = iter.clone().enumerate().skip(chars_to_skip);
330 while let Some((i, c)) = iter2.next() {
331 //trace!("(i,c)=({},'{}') {:?}",i,c,flags);
332 match c {
333 '0' ..= '9' => {
334 if flags.sig_capture_active {
335 if flags.sigfrac_capture_active {
336 sigfrac.push(c);
337 if c == '0' {
338 frac_trailing_zeros += 1;
339 } else {
340 frac_trailing_zeros = 0;
341 }
342 } else {
343 if c == '0' {
344 flags.prev_char_zero = true;
345 // Capture the character in the significand integer string
346 // if a non-zero character has already been detected, or if
347 // the radix is anything other than 10.
348 if flags.sig_nonzero_lead_detected || radix != 10 {
349 sigint.push(c);
350 sigint_digit_count += 1;
351 }
352 } else {
353 flags.prev_char_zero = false;
354 if radix == 2 && c != '1' {
355 // Binary numbers can only contain 0 or 1.
356 return Err(err!("{}", Self::errmsg(c, i, &s,
357 fmt!("but is not valid in base {}", radix));
358 String, Encode, Numeric, Mismatch));
359 }
360 if radix == 8 && (c == '8' || c == '9') {
361 // Octal numbers can only contain 0..7.
362 return Err(err!("{}", Self::errmsg(c, i, &s,
363 fmt!("but is not valid in base {}", radix));
364 String, Encode, Numeric, Mismatch));
365 }
366 sigint.push(c);
367 sigint_digit_count += 1;
368 flags.sig_nonzero_lead_detected = true;
369 }
370 }
371 } else {
372 if c == '0' {
373 if flags.exp_nonzero_lead_detected {
374 exp.push(c);
375 exp_digit_count += 1;
376 }
377 } else {
378 exp.push(c);
379 exp_digit_count += 1;
380 flags.exp_nonzero_lead_detected = true;
381 }
382 }
383 continue;
384 },
385 'a' ..= 'f' | 'A' ..= 'F' => {
386 if flags.sig_capture_active {
387 if (c == 'e' || c == 'E') && radix == 10 {
388 if flags.exp_detected {
389 return Err(err!("{}", Self::errmsg(c, i, &s,
390 fmt!("but an exp symbol had already been detected"));
391 String, Encode, Numeric, Mismatch));
392 } else {
393 flags.exp_detected = true;
394 flags.sig_capture_active = false;
395 flags.prev_char_zero = false;
396 continue;
397 }
398 }
399
400 if radix == 16 {
401 sigint.push(c.to_ascii_lowercase());
402 sigint_digit_count += 1;
403 } else {
404 return Err(err!("{}", Self::errmsg(c, i, &s,
405 fmt!("which is expected to represent a base {} \
406 number, but is only valid in a base 16 integer", radix));
407 String, Encode, Numeric, Mismatch));
408 }
409 flags.prev_char_zero = false;
410 continue;
411 } else {
412 return Err(err!("{}", Self::errmsg(c, i, &s,
413 fmt!("which is expected to represent a base {} \
414 number, but is only valid in a base 16 integer", radix));
415 String, Encode, Numeric, Mismatch));
416 }
417 },
418 '+' | '-' => {
419 if flags.sig_capture_active {
420 if sigint_digit_count > 0 {
421 return Err(err!("{}", Self::errmsg(c, i, &s,
422 fmt!("but the sign should be the first character"));
423 String, Encode, Numeric, Mismatch));
424 } else if flags.sig_sign_detected {
425 return Err(err!("{}", Self::errmsg(c, i, &s,
426 fmt!("but a sign had already been detected"));
427 String, Encode, Numeric, Mismatch));
428 } else if radix != 10 {
429 return Err(err!("{}", Self::errmsg(c, i, &s,
430 fmt!("with expected base {}, but signs are generally only used \
431 with base 10, other bases use non-signed negation", radix));
432 String, Encode, Numeric, Mismatch));
433 } else {
434 flags.sig_sign_detected = true;
435 if c == '-' {
436 flags.signeg = true;
437 }
438 flags.prev_char_zero = false;
439 continue;
440 }
441 } else {
442 if exp_digit_count > 0 {
443 return Err(err!("{}", Self::errmsg(c, i, &s,
444 fmt!("but the sign should be the first character of the exponent"));
445 String, Encode, Numeric, Mismatch));
446 } else if flags.exp_sign_detected {
447 return Err(err!("{}", Self::errmsg(c, i, &s,
448 fmt!("but a sign had already been detected in the exponent"));
449 String, Encode, Numeric, Mismatch));
450 } else {
451 flags.exp_sign_detected = true;
452 if c == '-' {
453 flags.expneg = true;
454 }
455 flags.prev_char_zero = false;
456 continue;
457 }
458 }
459 },
460 '.' => {
461 if flags.sig_capture_active {
462 if flags.point_detected {
463 return Err(err!("{}", Self::errmsg(c, i, &s,
464 fmt!("but a decimal point had already been detected"));
465 String, Encode, Numeric, Mismatch));
466 } else if radix != 10 {
467 return Err(err!("{}", Self::errmsg(c, i, &s,
468 fmt!("which appears to be base {}, and does not generally support \
469 decimal notation", radix));
470 String, Encode, Numeric, Mismatch));
471 } else {
472 flags.point_detected = true;
473 flags.sigfrac_capture_active = true;
474 if !flags.sig_nonzero_lead_detected {
475 sigint.push('0');
476 sigint_digit_count += 1;
477 }
478 flags.prev_char_zero = false;
479 continue;
480 }
481 } else {
482 return Err(err!("{}", Self::errmsg(c, i, &s,
483 fmt!("but this implementation does not support fractional exps"));
484 String, Encode, Numeric, NoImpl));
485 }
486 },
487 '_' => { // valid space characters
488 flags.prev_char_zero = false;
489 },
490 _ => {
491 return Err(err!("{}", Self::errmsg(c, i, &s,
492 fmt!("but is not valid for a number"));
493 String, Encode, Numeric, Invalid));
494 },
495 }
496 }
497
498 if sigint_digit_count == 0 {
499 if flags.prev_char_zero {
500 sigint.push('0');
501 sigint_digit_count += 1;
502 }
503 }
504
505 //if sigint_digit_count < 1 && !flags.sig_nonzero_lead_detected {
506 // return Err(err!(
507 // "The string '{}' does not represent a \
508 // number", s,
509 // ), String, Decode, Input, Numeric))
510 //}
511
512 if flags.exp_detected && exp_digit_count == 0 {
513 exp.push('0');
514 exp_digit_count += 1;
515 }
516
517 let digits = res!(Self::str_to_digits(&sigint));
518
519 Ok(NumberString {
520 src: s,
521 radix,
522 signeg: flags.signeg,
523 sigint,
524 digits,
525 sigfrac,
526 exp,
527 expneg: flags.expneg,
528 ftz: frac_trailing_zeros,
529 })
530 }
531}
532
533#[derive(Clone, Debug, Eq, PartialEq)]
534struct ValidationFlags {
535 radix_detected: bool,
536 sig_sign_detected: bool, // significand sign detected
537 signeg: bool,
538 sig_nonzero_lead_detected: bool, // applies to significand integer
539 sig_capture_active: bool, // capturing significand
540 point_detected: bool, // radix (i.e. decimal) point '.'
541 sigfrac_capture_active: bool, // capturing significand fraction part
542 exp_detected: bool, // exponent detected
543 exp_sign_detected: bool, // exponent sign detected
544 exp_nonzero_lead_detected: bool, // applies to exponent
545 expneg: bool,
546 prev_char_zero: bool, // a way to flag if the previous character was zero
547}
548
549impl Default for ValidationFlags {
550 fn default() -> Self {
551 Self {
552 radix_detected: false,
553 sig_sign_detected: false,
554 signeg: false,
555 sig_nonzero_lead_detected: false,
556 sig_capture_active: true,
557 point_detected: false,
558 sigfrac_capture_active: false,
559 exp_detected: false,
560 exp_sign_detected: false,
561 exp_nonzero_lead_detected: false,
562 expneg: false,
563 prev_char_zero: false,
564 }
565 }
566}
567
568/// Trait for formatting numbers with thousands separators.
569pub trait ThousandsSeparator {
570 /// Formats the number with the specified thousands separator character.
571 ///
572 /// # Arguments
573 /// * `sep` - The character to use as thousands separator (e.g., ',', ' ', '_').
574 ///
575 /// # Example
576 /// ```
577 /// use oxedyne_fe2o3_num::string::ThousandsSeparator;
578 ///
579 /// assert_eq!(1234567.with_sep(','), "1,234,567");
580 /// assert_eq!((-9876543).with_sep(' '), "-9 876 543");
581 /// ```
582 fn with_sep(&self, sep: char) -> String;
583
584 /// Formats the number with separators and specified decimal precision.
585 ///
586 /// # Arguments
587 /// * `sep` - The character to use as thousands separator (e.g., ',', ' ', '_').
588 /// * `decimals` - Number of decimal places to show (0 means no decimal point).
589 ///
590 /// # Example
591 /// ```
592 /// use oxedyne_fe2o3_num::string::ThousandsSeparator;
593 ///
594 /// assert_eq!(1234567.with_sep_dp(',', 2), "1,234,567.00");
595 /// assert_eq!(1234.567.with_sep_dp(' ', 1), "1 234.6");
596 /// assert_eq!(1234.with_sep_dp(',', 0), "1,234");
597 /// ```
598 fn with_sep_dp(&self, sep: char, decimals: usize) -> String;
599
600 /// Formats the number with comma thousands separators.
601 ///
602 /// # Example
603 /// ```
604 /// use oxedyne_fe2o3_num::string::ThousandsSeparator;
605 ///
606 /// assert_eq!(1234567.with_commas(), "1,234,567");
607 /// assert_eq!((-9876543).with_commas(), "-9,876,543");
608 /// ```
609 fn with_commas(&self) -> String {
610 self.with_sep(',')
611 }
612
613 /// Formats the number with comma thousands separators and specified decimal precision.
614 ///
615 /// # Arguments
616 /// * `decimals` - Number of decimal places to show (0 means no decimal point).
617 ///
618 /// # Example
619 /// ```
620 /// use oxedyne_fe2o3_num::string::ThousandsSeparator;
621 ///
622 /// assert_eq!(1234567.with_commas_dp(2), "1,234,567.00");
623 /// assert_eq!(1234.567.with_commas_dp(1), "1,234.6");
624 /// assert_eq!(1234.with_commas_dp(0), "1,234");
625 /// ```
626 fn with_commas_dp(&self, decimals: usize) -> String {
627 self.with_sep_dp(',', decimals)
628 }
629}
630
631/// Helper function to add separators to a string of digits.
632fn add_separators_to_digits(digits: &str, sep: char) -> String {
633 if digits.len() <= 3 {
634 return digits.to_string();
635 }
636
637 let mut result = String::with_capacity(digits.len() + (digits.len() - 1) / 3);
638 let chars: Vec<char> = digits.chars().collect();
639
640 for (i, &ch) in chars.iter().enumerate() {
641 if i > 0 && (chars.len() - i) % 3 == 0 {
642 result.push(sep);
643 }
644 result.push(ch);
645 }
646
647 result
648}
649
650macro_rules! impl_thousands_separator_for_int {
651 ($($t:ty),*) => {
652 $(
653 impl ThousandsSeparator for $t {
654 fn with_sep(&self, sep: char) -> String {
655 let s = self.to_string();
656 if let Some(stripped) = s.strip_prefix('-') {
657 format!("-{}", add_separators_to_digits(stripped, sep))
658 } else {
659 add_separators_to_digits(&s, sep)
660 }
661 }
662
663 fn with_sep_dp(&self, sep: char, decimals: usize) -> String {
664 let integer_part = self.with_sep(sep);
665 if decimals == 0 {
666 integer_part
667 } else {
668 format!("{}.{}", integer_part, "0".repeat(decimals))
669 }
670 }
671 }
672 )*
673 };
674}
675
676macro_rules! impl_thousands_separator_for_float {
677 ($($t:ty),*) => {
678 $(
679 impl ThousandsSeparator for $t {
680 fn with_sep(&self, sep: char) -> String {
681 let s = self.to_string();
682 let (sign, rest) = if let Some(stripped) = s.strip_prefix('-') {
683 ("-", stripped)
684 } else {
685 ("", s.as_str())
686 };
687
688 if let Some(dot_pos) = rest.find('.') {
689 let (integer_part, fractional_part) = rest.split_at(dot_pos);
690 format!("{}{}{}", sign, add_separators_to_digits(integer_part, sep), fractional_part)
691 } else {
692 format!("{}{}", sign, add_separators_to_digits(rest, sep))
693 }
694 }
695
696 fn with_sep_dp(&self, sep: char, decimals: usize) -> String {
697 let formatted = if decimals == 0 {
698 format!("{:.0}", self)
699 } else {
700 format!("{:.1$}", self, decimals)
701 };
702
703 let (sign, rest) = if let Some(stripped) = formatted.strip_prefix('-') {
704 ("-", stripped)
705 } else {
706 ("", formatted.as_str())
707 };
708
709 if decimals == 0 {
710 format!("{}{}", sign, add_separators_to_digits(rest, sep))
711 } else if let Some(dot_pos) = rest.find('.') {
712 let (integer_part, fractional_part) = rest.split_at(dot_pos);
713 format!("{}{}{}", sign, add_separators_to_digits(integer_part, sep), fractional_part)
714 } else {
715 format!("{}{}", sign, add_separators_to_digits(rest, sep))
716 }
717 }
718 }
719 )*
720 };
721}
722
723// Implement for all standard integer types
724impl_thousands_separator_for_int!(i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize);
725
726// Implement for all standard floating-point types
727impl_thousands_separator_for_float!(f32, f64);
728
729#[cfg(test)]
730mod tests {
731 use super::*;
732
733 #[test]
734 fn test_empty_00() -> Outcome<()> {
735 let s = String::from("");
736 match NumberString::validate(&s) {
737 Ok(ns) => Err(err!(
738 "The empty string '{}' should have triggered an error but the result was \
739 ns = {:?}", s, ns;
740 String, Decode)),
741 Err(_) => Ok(()),
742 }
743 }
744
745 #[test]
746 fn test_empty_01() -> Outcome<()> {
747 let s = String::from("-");
748 match NumberString::validate(&s) {
749 Ok(ns) => Err(err!(
750 "The empty string '{}' should have triggered an error but the result was \
751 ns = {:?}", s, ns;
752 String, Decode)),
753 Err(_) => Ok(()),
754 }
755 }
756
757 #[test]
758 fn test_integers_base_10_000() -> Outcome<()> {
759 for i in 1..10001 {
760 let s0 = i.to_string();
761 let digits = res!(string_to_digit_vec(&s0));
762 let ns = res!(NumberString::validate(&s0));
763 let expected = NumberString {
764 src: s0.clone(),
765 sigint: s0,
766 digits: digits,
767 ..Default::default()
768 };
769 assert_eq!(ns, expected);
770 }
771 Ok(())
772 }
773
774 #[test]
775 fn test_integers_base_10_010() -> Outcome<()> {
776 for i in -10001..0i32 {
777 let s0 = i.to_string();
778 let s00 = i.abs().to_string();
779 let digits = res!(string_to_digit_vec(&s00));
780 let ns = res!(NumberString::validate(&s0));
781 let expected = NumberString {
782 src: s0,
783 signeg: true,
784 sigint: s00,
785 digits: digits,
786 ..Default::default()
787 };
788 assert_eq!(ns, expected);
789 }
790 Ok(())
791 }
792
793 #[test]
794 fn test_leading_zeros_000() -> Outcome<()> {
795 for i in 1..10001 {
796 let s0 = i.to_string();
797 let mut s1 = s0.clone();
798 s1.insert_str(0, "0000");
799 let digits = res!(string_to_digit_vec(&s0));
800 let ns = res!(NumberString::validate(&s1));
801 let expected = NumberString {
802 src: s1,
803 sigint: s0,
804 digits: digits,
805 ..Default::default()
806 };
807 assert_eq!(ns, expected);
808 }
809 Ok(())
810 }
811
812 #[test]
813 fn test_leading_zeros_010() -> Outcome<()> {
814 let s = String::from("0");
815 let ns = res!(NumberString::validate(&s));
816 let expected = NumberString {
817 src: s,
818 sigint: String::from("0"),
819 digits: vec![0],
820 ..Default::default()
821 };
822 assert_eq!(ns, expected);
823 Ok(())
824 }
825
826 #[test]
827 fn test_signs_000() -> Outcome<()> {
828 let s = String::from("-0");
829 let ns = res!(NumberString::validate(&s));
830 let expected = NumberString {
831 src: s,
832 signeg: true,
833 sigint: String::from("0"),
834 digits: vec![0],
835 ..Default::default()
836 };
837 assert_eq!(ns, expected);
838 Ok(())
839 }
840
841 #[test]
842 fn test_signs_010() -> Outcome<()> {
843 let s = String::from("+0");
844 let ns = res!(NumberString::validate(&s));
845 let expected = NumberString {
846 src: s,
847 sigint: String::from("0"),
848 digits: vec![0],
849 ..Default::default()
850 };
851 assert_eq!(ns, expected);
852 Ok(())
853 }
854
855 #[test]
856 fn test_signs_020() -> Outcome<()> {
857 // Space should trigger error
858 let s = String::from("-");
859 match NumberString::validate(&s) {
860 Ok(ns) => Err(err!(
861 "The string '{}' should have triggered an error but the result was \
862 ns = {:?}", s, ns;
863 String, Decode)),
864 Err(_) => Ok(()),
865 }
866 }
867
868 #[test]
869 fn test_signs_030() -> Outcome<()> {
870 // Space should trigger error
871 let s = String::from("+");
872 match NumberString::validate(&s) {
873 Ok(ns) => Err(err!(
874 "The string '{}' should have triggered an error but the result was \
875 ns = {:?}", s, ns;
876 String, Decode)),
877 Err(_) => Ok(()),
878 }
879 }
880
881 #[test]
882 fn test_signs_040() -> Outcome<()> {
883 // Space should trigger error
884 let s = String::from("++10");
885 match NumberString::validate(&s) {
886 Ok(ns) => Err(err!(
887 "The extra sign in '{}' should have triggered an error but the result was \
888 ns = {:?}", s, ns;
889 String, Decode)),
890 Err(_) => Ok(()),
891 }
892 }
893
894 #[test]
895 fn test_signs_050() -> Outcome<()> {
896 // Space should trigger error
897 let s = String::from("--10");
898 match NumberString::validate(&s) {
899 Ok(ns) => Err(err!(
900 "The extra sign in '{}' should have triggered an error but the result was \
901 ns = {:?}", s, ns;
902 String, Decode)),
903 Err(_) => Ok(()),
904 }
905 }
906
907 #[test]
908 fn test_signs_060() -> Outcome<()> {
909 // Space should trigger error
910 let s = String::from("-+10");
911 match NumberString::validate(&s) {
912 Ok(ns) => Err(err!(
913 "The extra sign in '{}' should have triggered an error but the result was \
914 ns = {:?}", s, ns;
915 String, Decode)),
916 Err(_) => Ok(()),
917 }
918 }
919
920 #[test]
921 fn test_signs_070() -> Outcome<()> {
922 // Space should trigger error
923 let s = String::from("-1+0");
924 match NumberString::validate(&s) {
925 Ok(ns) => Err(err!(
926 "The extra sign in '{}' should have triggered an error but the result was \
927 ns = {:?}", s, ns;
928 String, Decode)),
929 Err(_) => Ok(()),
930 }
931 }
932
933 #[test]
934 fn test_signs_080() -> Outcome<()> {
935 // Space should trigger error
936 let s = String::from("-1000+");
937 match NumberString::validate(&s) {
938 Ok(ns) => Err(err!(
939 "The extra sign in '{}' should have triggered an error but the result was \
940 ns = {:?}", s, ns;
941 String, Decode)),
942 Err(_) => Ok(()),
943 }
944 }
945
946 #[test]
947 fn test_with_space_000() -> Outcome<()> {
948 // Space should trigger error
949 let s = String::from("1 234 567");
950 match NumberString::validate(&s) {
951 Ok(ns) => Err(err!(
952 "The first space in '{}' should have triggered an error but the result was \
953 ns = {:?}", s, ns;
954 String, Decode)),
955 Err(_) => Ok(()),
956 }
957 }
958
959 #[test]
960 fn test_binary_000() -> Outcome<()> {
961 let s = String::from("0b1010");
962 let ns = res!(NumberString::validate(&s));
963 let expected = NumberString {
964 src: s,
965 radix: 2,
966 sigint: String::from("1010"),
967 digits: vec![1,0,1,0],
968 ..Default::default()
969 };
970 assert_eq!(ns, expected);
971 Ok(())
972 }
973
974 #[test]
975 fn test_binary_005() -> Outcome<()> {
976 let s = String::from("0b_10_10");
977 let ns = res!(NumberString::validate(&s));
978 let expected = NumberString {
979 src: s,
980 radix: 2,
981 sigint: String::from("1010"),
982 digits: vec![1,0,1,0],
983 ..Default::default()
984 };
985 assert_eq!(ns, expected);
986 Ok(())
987 }
988
989 #[test]
990 fn test_binary_010() -> Outcome<()> {
991 // Invalid binary digit
992 let s = String::from("0b2");
993 match NumberString::validate(&s) {
994 Ok(ns) => Err(err!(
995 "The non-binary digit 2 in '{}' should have triggered an error but the result was \
996 ns = {:?}", s, ns;
997 String, Decode)),
998 Err(_) => Ok(()),
999 }
1000 }
1001
1002 #[test]
1003 fn test_octal_000() -> Outcome<()> {
1004 let s = String::from("0o1234567");
1005 let ns = res!(NumberString::validate(&s));
1006 let expected = NumberString {
1007 src: s,
1008 radix: 8,
1009 sigint: String::from("1234567"),
1010 digits: (1..8).collect(),
1011 ..Default::default()
1012 };
1013 assert_eq!(ns, expected);
1014 Ok(())
1015 }
1016
1017 #[test]
1018 fn test_octal_010() -> Outcome<()> {
1019 // Invalid octal digit
1020 let s = String::from("0o8");
1021 match NumberString::validate(&s) {
1022 Ok(ns) => Err(err!(
1023 "The non-octal digit 8 in '{}' should have triggered an error but the result was \
1024 ns = {:?}", s, ns;
1025 String, Decode)),
1026 Err(_) => Ok(()),
1027 }
1028 }
1029
1030 #[test]
1031 fn test_hex_000() -> Outcome<()> {
1032 let s = String::from("0x123456789abcdef");
1033 let ns = res!(NumberString::validate(&s));
1034 let expected = NumberString {
1035 src: s,
1036 radix: 16,
1037 sigint: String::from("123456789abcdef"),
1038 digits: (1..16).collect(),
1039 ..Default::default()
1040 };
1041 assert_eq!(ns, expected);
1042 Ok(())
1043 }
1044
1045 #[test]
1046 fn test_hex_010() -> Outcome<()> {
1047 // Invalid hex digit
1048 let s = String::from("0xg");
1049 match NumberString::validate(&s) {
1050 Ok(ns) => Err(err!(
1051 "The non-hex digit g in '{}' should have triggered an error but the result was \
1052 ns = {:?}", s, ns;
1053 String, Decode)),
1054 Err(_) => Ok(()),
1055 }
1056 }
1057
1058 #[test]
1059 fn test_hex_020() -> Outcome<()> {
1060 // Invalid hex digit
1061 let s = String::from("0xo");
1062 match NumberString::validate(&s) {
1063 Ok(ns) => Err(err!(
1064 "The non-hex digit o in '{}' should have triggered an error but the result was \
1065 ns = {:?}", s, ns;
1066 String, Decode)),
1067 Err(_) => Ok(()),
1068 }
1069 }
1070
1071 #[test]
1072 fn test_hex_030() -> Outcome<()> {
1073 // Invalid hex digit
1074 let s = String::from("0x__o");
1075 match NumberString::validate(&s) {
1076 Ok(ns) => Err(err!(
1077 "The non-hex digit o in '{}' should have triggered an error but the result was \
1078 ns = {:?}", s, ns;
1079 String, Decode)),
1080 Err(_) => Ok(()),
1081 }
1082 }
1083
1084 #[test]
1085 fn test_hex_040() -> Outcome<()> {
1086 // Invalid hex digit
1087 let s = String::from("0xx");
1088 match NumberString::validate(&s) {
1089 Ok(ns) => Err(err!(
1090 "The non-hex digit x in '{}' should have triggered an error but the result was \
1091 ns = {:?}", s, ns;
1092 String, Decode)),
1093 Err(_) => Ok(()),
1094 }
1095 }
1096
1097 #[test]
1098 fn test_hex_050() -> Outcome<()> {
1099 // Invalid hex digit
1100 let s = String::from("0x___x");
1101 match NumberString::validate(&s) {
1102 Ok(ns) => Err(err!(
1103 "The non-hex digit x in '{}' should have triggered an error but the result was \
1104 ns = {:?}", s, ns;
1105 String, Decode)),
1106 Err(_) => Ok(()),
1107 }
1108 }
1109
1110 #[test]
1111 fn test_hex_060() -> Outcome<()> {
1112 // Invalid hex digit
1113 let s = String::from("afe123");
1114 match NumberString::validate(&s) {
1115 Ok(ns) => Err(err!(
1116 "The hex number '{}' should have triggered an error because of the absence of \
1117 a '0x' prefix but the result was ns = {:?}", s, ns;
1118 String, Decode)),
1119 Err(_) => Ok(()),
1120 }
1121 }
1122
1123 #[test]
1124 fn test_hex_100() -> Outcome<()> {
1125 let s1 = String::from("0xf0");
1126 let ns1 = NumberString::validate(&s1);
1127 test!("{:?}", ns1);
1128 Ok(())
1129 }
1130
1131 #[test]
1132 fn test_decimals_000() -> Outcome<()> {
1133 for i in 1..101 {
1134 for j in 1..101 {
1135 let si = i.to_string();
1136 let sf = j.to_string();
1137 let mut s = sf.clone();
1138 s.insert(0, '.');
1139 s.insert_str(0, &si);
1140 let digits = res!(string_to_digit_vec(&si));
1141 let ns = res!(NumberString::validate(&s));
1142 let ftz = NumberString::count_trailing_zeros(&s);
1143 let expected = NumberString {
1144 src: s,
1145 sigint: si,
1146 digits: digits,
1147 sigfrac:sf,
1148 exp: String::from(""),
1149 ftz,
1150 ..Default::default()
1151 };
1152 assert_eq!(ns, expected);
1153 }
1154 }
1155 Ok(())
1156 }
1157
1158 #[test]
1159 fn test_decimals_010() -> Outcome<()> {
1160 for i in -101..0i32 {
1161 for j in 1..101 {
1162 let si = i.to_string();
1163 let si0 = i.abs().to_string();
1164 let sf = j.to_string();
1165 let mut s = sf.clone();
1166 s.insert(0, '.');
1167 s.insert_str(0, &si);
1168 let digits = res!(string_to_digit_vec(&si0));
1169 let ns = res!(NumberString::validate(&s));
1170 let ftz = NumberString::count_trailing_zeros(&s);
1171 let expected = NumberString {
1172 src: s,
1173 signeg: i < 0,
1174 sigint: si0,
1175 digits: digits,
1176 sigfrac:sf,
1177 ftz,
1178 ..Default::default()
1179 };
1180 assert_eq!(ns, expected);
1181 }
1182 }
1183 Ok(())
1184 }
1185
1186 #[test]
1187 fn test_decimals_020() -> Outcome<()> {
1188 let s = String::from("0.0");
1189 let ns = res!(NumberString::validate(&s));
1190 let expected = NumberString {
1191 src: s,
1192 sigint: String::from("0"),
1193 digits: vec![0],
1194 sigfrac:String::from("0"),
1195 ftz: 1,
1196 ..Default::default()
1197 };
1198 assert_eq!(ns, expected);
1199 Ok(())
1200 }
1201
1202 #[test]
1203 fn test_decimals_030() -> Outcome<()> {
1204 let s = String::from("+0.0");
1205 let ns = res!(NumberString::validate(&s));
1206 let expected = NumberString {
1207 src: s,
1208 sigint: String::from("0"),
1209 digits: vec![0],
1210 sigfrac:String::from("0"),
1211 ftz: 1,
1212 ..Default::default()
1213 };
1214 assert_eq!(ns, expected);
1215 Ok(())
1216 }
1217
1218 #[test]
1219 fn test_decimals_040() -> Outcome<()> {
1220 let s = String::from("000.0000");
1221 let ns = res!(NumberString::validate(&s));
1222 let expected = NumberString {
1223 src: s,
1224 sigint: String::from("0"),
1225 digits: vec![0],
1226 sigfrac:String::from("0000"),
1227 ftz: 4,
1228 ..Default::default()
1229 };
1230 assert_eq!(ns, expected);
1231 Ok(())
1232 }
1233
1234 #[test]
1235 fn test_sci_000() -> Outcome<()> {
1236 for i in 1..11 {
1237 for j in 1..11 {
1238 for k in -10..11i32 {
1239 let si = i.to_string();
1240 let sf = j.to_string();
1241 let e = k.to_string();
1242 let e0 = k.abs().to_string();
1243 let mut s = e.clone();
1244 s.insert(0, 'e');
1245 s.insert_str(0, &sf);
1246 s.insert(0, '.');
1247 s.insert_str(0, &si);
1248 let digits = res!(string_to_digit_vec(&si));
1249 let ns = res!(NumberString::validate(&s));
1250 let expected = NumberString {
1251 src: s,
1252 sigint: si,
1253 digits: digits,
1254 sigfrac:sf.clone(),
1255 exp: e0,
1256 expneg: k < 0,
1257 ftz: NumberString::count_trailing_zeros(&sf),
1258 ..Default::default()
1259 };
1260 assert_eq!(ns, expected);
1261 }
1262 }
1263 }
1264 Ok(())
1265 }
1266
1267 #[test]
1268 fn test_sci_010() -> Outcome<()> {
1269 for i in 1..11 {
1270 for j in 1..11 {
1271 for sign in "-+".chars() {
1272 for k in 0..11 {
1273 let si = i.to_string();
1274 let sf = j.to_string();
1275 let e0 = k.to_string();
1276 let mut e = e0.clone();
1277 e.insert_str(0, "000");
1278 e.insert(0, sign);
1279 let mut s = e.clone();
1280 s.insert(0, 'e');
1281 s.insert_str(0, &sf);
1282 s.insert(0, '.');
1283 s.insert_str(0, &si);
1284 let digits = res!(string_to_digit_vec(&si));
1285 let ns = res!(NumberString::validate(&s));
1286 let expected = NumberString {
1287 src: s,
1288 sigint: si,
1289 digits: digits,
1290 sigfrac:sf.clone(),
1291 exp: e0,
1292 expneg: sign == '-',
1293 ftz: NumberString::count_trailing_zeros(&sf),
1294 ..Default::default()
1295 };
1296 assert_eq!(ns, expected);
1297 }
1298 }
1299 }
1300 }
1301 Ok(())
1302 }
1303
1304 #[test]
1305 fn test_a_negative_exponent_divides_015() -> Outcome<()> {
1306 // Dated 2026-09-23: the exponent's sign was dropped, so 1e-7 read as ten million.
1307 // The oracle is the bigdecimal crate's own parser.
1308 for s in ["1e-7", "2.5e-3", "-1.5E-2", "5e-324", "1e7", "-0120.03450e+003", "0.3"] {
1309 let ns = res!(NumberString::validate(s));
1310 let got = res!(ns.as_bigdecimal());
1311 let want = res!(BigDecimal::from_str(s), Decode, Numeric);
1312 assert_eq!(got, want, "{} read as {}", s, got);
1313 }
1314 Ok(())
1315 }
1316
1317 #[test]
1318 fn test_sci_020() -> Outcome<()> {
1319 let s = String::from("-0120.03450e+003");
1320 let ns = res!(NumberString::validate(&s));
1321 let expected = NumberString {
1322 src: s,
1323 signeg: true,
1324 sigint: String::from("120"),
1325 digits: vec![1,2,0],
1326 sigfrac:String::from("03450"),
1327 exp: String::from("3"),
1328 ftz: 1,
1329 ..Default::default()
1330 };
1331 assert_eq!(ns, expected);
1332 Ok(())
1333 }
1334
1335 #[test]
1336 fn test_sci_050() -> Outcome<()> {
1337 let s = String::from("0.0e0");
1338 let ns = res!(NumberString::validate(&s));
1339 let expected = NumberString {
1340 src: s,
1341 sigint: String::from("0"),
1342 digits: vec![0],
1343 sigfrac:String::from("0"),
1344 exp: String::from("0"),
1345 ftz: 1,
1346 ..Default::default()
1347 };
1348 assert_eq!(ns, expected);
1349 Ok(())
1350 }
1351
1352 #[test]
1353 fn test_sci_100() -> Outcome<()> {
1354 let s = String::from("0.0f0");
1355 match NumberString::validate(&s) {
1356 Ok(ns) => Err(err!(
1357 "The invalid exp designator f in '{}' should have triggered an error but \
1358 the result was ns = {:?}", s, ns;
1359 String, Decode)),
1360 Err(_) => Ok(()),
1361 }
1362 }
1363
1364 #[test]
1365 fn test_string_to_bigdecimal_000() -> Outcome<()> {
1366 let s = String::from("0");
1367 let ns = res!(NumberString::validate(&s));
1368 let d = res!(ns.as_bigdecimal());
1369 assert_eq!(s, fmt!("{}", d));
1370 Ok(())
1371 }
1372
1373 #[test]
1374 fn test_string_to_bigdecimal_010() -> Outcome<()> {
1375 let s = String::from("1234");
1376 let ns = res!(NumberString::validate(&s));
1377 let d = res!(ns.as_bigdecimal());
1378 assert_eq!(s, fmt!("{}", d));
1379 Ok(())
1380 }
1381
1382 #[test]
1383 fn test_string_to_bigdecimal_020() -> Outcome<()> {
1384 let s = String::from("1234.5678");
1385 let ns = res!(NumberString::validate(&s));
1386 let d = res!(ns.as_bigdecimal());
1387 assert_eq!(s, fmt!("{}", d));
1388 Ok(())
1389 }
1390
1391 #[test]
1392 fn test_string_to_bigdecimal_030() -> Outcome<()> {
1393 let s = String::from("-1234.5678e5");
1394 let ns = res!(NumberString::validate(&s));
1395 let d = res!(ns.as_bigdecimal());
1396 assert_eq!(String::from("-123456780"), fmt!("{}", d));
1397 Ok(())
1398 }
1399
1400 #[test]
1401 fn test_string_to_bigdecimal_040() -> Outcome<()> {
1402 let s = String::from("-0.000001234");
1403 let ns = res!(NumberString::validate(&s));
1404 let d = res!(ns.as_bigdecimal());
1405 assert_eq!(s, fmt!("{}", d));
1406 Ok(())
1407 }
1408
1409 #[test]
1410 fn test_string_to_bigdecimal_140() -> Outcome<()> {
1411 let s = String::from("0b0");
1412 let ns = res!(NumberString::validate(&s));
1413 let d = res!(ns.as_bigdecimal());
1414 assert_eq!(String::from("0"), fmt!("{}", d));
1415 Ok(())
1416 }
1417
1418 #[test]
1419 fn test_string_to_bigdecimal_150() -> Outcome<()> {
1420 let s = String::from("0b1010");
1421 let ns = res!(NumberString::validate(&s));
1422 let d = res!(ns.as_bigdecimal());
1423 assert_eq!(String::from("10"), fmt!("{}", d));
1424 Ok(())
1425 }
1426
1427 #[test]
1428 fn test_string_to_bigdecimal_160() -> Outcome<()> {
1429 let s = String::from("0o30071");
1430 let ns = res!(NumberString::validate(&s));
1431 let d = res!(ns.as_bigdecimal());
1432 assert_eq!(String::from("12345"), fmt!("{}", d));
1433 Ok(())
1434 }
1435
1436 #[test]
1437 fn test_string_to_bigdecimal_170() -> Outcome<()> {
1438 let s = String::from("0x3039");
1439 let ns = res!(NumberString::validate(&s));
1440 let d = res!(ns.as_bigdecimal());
1441 assert_eq!(String::from("12345"), fmt!("{}", d));
1442 Ok(())
1443 }
1444
1445 #[test]
1446 fn test_string_to_bigdecimal_180() -> Outcome<()> {
1447 // Use of a negative sign ok for all radices.
1448 let s = String::from("-0x3039");
1449 let ns = res!(NumberString::validate(&s));
1450 let d = res!(ns.as_bigdecimal());
1451 assert_eq!(String::from("-12345"), fmt!("{}", d));
1452 Ok(())
1453 }
1454}