1use core::fmt;
54
55use crate::core_type::{ParseError, D38};
56
57#[cfg(feature = "alloc")]
58extern crate alloc;
59
60impl<const SCALE: u32> fmt::LowerExp for D38<SCALE> {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 format_exp(self.0, SCALE, false, f)
93 }
94}
95
96impl<const SCALE: u32> fmt::UpperExp for D38<SCALE> {
97 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 format_exp(self.0, SCALE, true, f)
115 }
116}
117
118fn format_exp(raw: i128, scale: u32, upper: bool, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 let exp_char = if upper { 'E' } else { 'e' };
128 if raw == 0 {
129 return write!(f, "0{exp_char}0");
130 }
131 let negative = raw < 0;
132 let mag: u128 = raw.unsigned_abs();
133
134 let mut buf = [0u8; 40];
137 let mut len = 0usize;
138 let mut n = mag;
139 while n > 0 {
140 let digit = (n % 10) as u8;
141 buf[len] = b'0' + digit;
142 len += 1;
143 n /= 10;
144 }
145 buf[..len].reverse();
146 let digits = &buf[..len];
147
148 let exp: i32 = (len as i32 - 1) - scale as i32;
150
151 let mut frac_end = len;
153 while frac_end > 1 && digits[frac_end - 1] == b'0' {
154 frac_end -= 1;
155 }
156 let mantissa_int = digits[0] as char;
157 let mantissa_frac = &digits[1..frac_end];
158
159 if negative {
160 f.write_str("-")?;
161 }
162 if mantissa_frac.is_empty() {
163 write!(f, "{mantissa_int}{exp_char}{exp}")
165 } else {
166 f.write_fmt(format_args!("{mantissa_int}."))?;
167 let frac_str = core::str::from_utf8(mantissa_frac).map_err(|_| fmt::Error)?;
169 write!(f, "{frac_str}{exp_char}{exp}")
170 }
171}
172
173pub(crate) fn parse_decimal_bits<const SCALE: u32>(s: &str) -> Result<i128, ParseError> {
186 parse_decimal::<SCALE>(s).map(super::core_type::D38::to_bits)
187}
188
189fn parse_decimal<const SCALE: u32>(s: &str) -> Result<D38<SCALE>, ParseError> {
190 if s.is_empty() {
191 return Err(ParseError::Empty);
192 }
193
194 let bytes = s.as_bytes();
195 let mut idx = 0usize;
196
197 let negative = match bytes[0] {
199 b'-' => {
200 idx += 1;
201 true
202 }
203 b'+' => {
204 idx += 1;
205 false
206 }
207 _ => false,
208 };
209 if idx == bytes.len() {
210 return Err(ParseError::SignOnly);
212 }
213
214 let mut dot_pos: Option<usize> = None;
217 {
218 let mut i = idx;
219 while i < bytes.len() {
220 let c = bytes[i];
221 match c {
222 b'0'..=b'9' => {}
223 b'.' => {
224 if dot_pos.is_some() {
225 return Err(ParseError::InvalidChar);
228 }
229 dot_pos = Some(i);
230 }
231 b'e' | b'E' => {
232 return Err(ParseError::ScientificNotation);
233 }
234 _ => return Err(ParseError::InvalidChar),
235 }
236 i += 1;
237 }
238 }
239
240 let (int_str, frac_str) = match dot_pos {
241 Some(p) => (&bytes[idx..p], &bytes[p + 1..]),
242 None => (&bytes[idx..], &[][..]),
243 };
244
245 if dot_pos.is_some() {
246 if int_str.is_empty() || frac_str.is_empty() {
248 return Err(ParseError::MissingDigits);
249 }
250 } else if int_str.is_empty() {
251 return Err(ParseError::SignOnly);
252 }
253
254 if int_str.len() > 1 && int_str[0] == b'0' {
256 return Err(ParseError::LeadingZero);
257 }
258
259 if frac_str.len() > SCALE as usize {
261 return Err(ParseError::OverlongFractional);
262 }
263
264 let multiplier: u128 = 10u128.pow(SCALE);
267
268 let mut int_value: u128 = 0;
270 for &b in int_str {
271 let digit = u128::from(b - b'0');
272 int_value = match int_value.checked_mul(10).and_then(|v| v.checked_add(digit)) {
273 Some(v) => v,
274 None => return Err(ParseError::OutOfRange),
275 };
276 }
277 let int_scaled = match int_value.checked_mul(multiplier) {
278 Some(v) => v,
279 None => return Err(ParseError::OutOfRange),
280 };
281
282 let mut frac_value: u128 = 0;
285 let frac_len = frac_str.len();
286 for &b in frac_str {
287 let digit = u128::from(b - b'0');
288 frac_value = match frac_value
289 .checked_mul(10)
290 .and_then(|v| v.checked_add(digit))
291 {
292 Some(v) => v,
293 None => return Err(ParseError::OutOfRange),
294 };
295 }
296 let pad = (SCALE as usize) - frac_len;
297 if pad > 0 {
298 let pad_factor: u128 = 10u128.pow(pad as u32);
299 frac_value = match frac_value.checked_mul(pad_factor) {
300 Some(v) => v,
301 None => return Err(ParseError::OutOfRange),
302 };
303 }
304
305 let combined = match int_scaled.checked_add(frac_value) {
306 Some(v) => v,
307 None => return Err(ParseError::OutOfRange),
308 };
309
310 let raw: i128 = if negative {
313 let neg_min_abs: u128 = (i128::MAX as u128) + 1;
314 if combined > neg_min_abs {
315 return Err(ParseError::OutOfRange);
316 }
317 if combined == neg_min_abs {
318 i128::MIN
319 } else {
320 -(combined as i128)
321 }
322 } else {
323 if combined > i128::MAX as u128 {
324 return Err(ParseError::OutOfRange);
325 }
326 combined as i128
327 };
328
329 Ok(D38::<SCALE>::from_bits(raw))
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335 use crate::core_type::{D38s12, D38};
336 #[cfg(feature = "alloc")]
337 use alloc::format;
338 #[cfg(feature = "alloc")]
339 use alloc::string::ToString;
340
341 #[cfg(feature = "alloc")]
345 #[test]
346 fn display_zero_renders() {
347 assert_eq!(D38s12::ZERO.to_string(), "0.000000000000");
348 }
349
350 #[cfg(feature = "alloc")]
352 #[test]
353 fn display_one_renders() {
354 assert_eq!(D38s12::ONE.to_string(), "1.000000000000");
355 }
356
357 #[cfg(feature = "alloc")]
359 #[test]
360 fn display_one_point_five_renders() {
361 let v = D38s12::from_bits(1_500_000_000_000);
362 assert_eq!(v.to_string(), "1.500000000000");
363 }
364
365 #[cfg(feature = "alloc")]
367 #[test]
368 fn display_negative_renders() {
369 let v = D38s12::from_bits(-1_500_000_000_000);
370 assert_eq!(v.to_string(), "-1.500000000000");
371 }
372
373 #[cfg(feature = "alloc")]
375 #[test]
376 fn display_subunit_keeps_leading_zeros() {
377 let v = D38s12::from_bits(1_000_000_000);
379 assert_eq!(v.to_string(), "0.001000000000");
380 }
381
382 #[cfg(feature = "alloc")]
385 #[test]
386 fn display_max_does_not_panic() {
387 let s = D38s12::MAX.to_string();
388 assert_eq!(s, "170141183460469231731687303.715884105727");
389 }
390
391 #[cfg(feature = "alloc")]
395 #[test]
396 fn display_min_does_not_panic() {
397 let s = D38s12::MIN.to_string();
398 assert_eq!(s, "-170141183460469231731687303.715884105728");
399 }
400
401 #[cfg(feature = "alloc")]
403 #[test]
404 fn display_scale_zero_no_dot() {
405 type D0 = D38<0>;
406 assert_eq!(D0::ONE.to_string(), "1");
407 assert_eq!(D0::ZERO.to_string(), "0");
408 assert_eq!(D0::from_bits(-42).to_string(), "-42");
409 }
410
411 #[cfg(feature = "alloc")]
415 #[test]
416 fn debug_includes_scale_and_value() {
417 let v = D38s12::from_bits(1_500_000_000_000);
418 let debug_str = format!("{v:?}");
419 assert_eq!(debug_str, "D38<12>(1.500000000000)");
420 }
421
422 #[cfg(feature = "alloc")]
424 #[test]
425 fn debug_other_scale() {
426 type D6 = D38<6>;
427 let v = D6::ZERO;
428 assert_eq!(format!("{v:?}"), "D38<6>(0.000000)");
429 }
430
431 #[cfg(feature = "alloc")]
435 #[test]
436 fn lower_exp_one() {
437 let v = D38s12::ONE;
438 assert_eq!(format!("{v:e}"), "1e0");
439 }
440
441 #[cfg(feature = "alloc")]
443 #[test]
444 fn lower_exp_one_point_five() {
445 let v = D38s12::from_bits(1_500_000_000_000);
446 assert_eq!(format!("{v:e}"), "1.5e0");
447 }
448
449 #[cfg(feature = "alloc")]
451 #[test]
452 fn lower_exp_fifteen() {
453 let v = D38s12::from_bits(15_000_000_000_000);
454 assert_eq!(format!("{v:e}"), "1.5e1");
455 }
456
457 #[cfg(feature = "alloc")]
459 #[test]
460 fn lower_exp_zero() {
461 assert_eq!(format!("{:e}", D38s12::ZERO), "0e0");
462 }
463
464 #[cfg(feature = "alloc")]
466 #[test]
467 fn lower_exp_subunit_negative_exponent() {
468 let v = D38s12::from_bits(1_500_000_000);
470 assert_eq!(format!("{v:e}"), "1.5e-3");
471 }
472
473 #[cfg(feature = "alloc")]
475 #[test]
476 fn lower_exp_negative() {
477 let v = D38s12::from_bits(-1_500_000_000_000);
478 assert_eq!(format!("{v:e}"), "-1.5e0");
479 }
480
481 #[cfg(feature = "alloc")]
483 #[test]
484 fn upper_exp_uses_capital_e() {
485 let v = D38s12::from_bits(1_500_000_000_000);
486 assert_eq!(format!("{v:E}"), "1.5E0");
487 }
488
489 #[cfg(feature = "alloc")]
494 #[test]
495 fn lower_hex_is_storage() {
496 assert_eq!(format!("{:x}", D38s12::ONE), "e8d4a51000");
497 }
498
499 #[cfg(feature = "alloc")]
501 #[test]
502 fn upper_hex_is_storage() {
503 assert_eq!(format!("{:X}", D38s12::ONE), "E8D4A51000");
504 }
505
506 #[cfg(feature = "alloc")]
508 #[test]
509 fn octal_zero() {
510 assert_eq!(format!("{:o}", D38s12::ZERO), "0");
511 }
512
513 #[cfg(feature = "alloc")]
515 #[test]
516 fn binary_one() {
517 let s = format!("{:b}", D38s12::ONE);
519 assert_eq!(s, "1110100011010100101001010001000000000000");
520 }
521
522 #[cfg(feature = "alloc")]
525 #[test]
526 fn parse_error_display_messages() {
527 assert_eq!(ParseError::Empty.to_string(), "empty input");
528 assert_eq!(
529 ParseError::SignOnly.to_string(),
530 "sign with no digits"
531 );
532 assert_eq!(
533 ParseError::LeadingZero.to_string(),
534 "redundant leading zero in integer part"
535 );
536 assert_eq!(
537 ParseError::OverlongFractional.to_string(),
538 "fractional part exceeds SCALE digits"
539 );
540 assert_eq!(
541 ParseError::ScientificNotation.to_string(),
542 "scientific notation not accepted"
543 );
544 assert_eq!(
545 ParseError::InvalidChar.to_string(),
546 "invalid character"
547 );
548 assert_eq!(
549 ParseError::OutOfRange.to_string(),
550 "value out of representable range"
551 );
552 assert_eq!(
553 ParseError::MissingDigits.to_string(),
554 "decimal point with no adjacent digits"
555 );
556 }
557
558 #[test]
561 fn from_str_zero() {
562 let v: D38s12 = "0".parse().unwrap();
563 assert_eq!(v, D38s12::ZERO);
564 let v: D38s12 = "0.0".parse().unwrap();
565 assert_eq!(v, D38s12::ZERO);
566 }
567
568 #[test]
569 fn from_str_one() {
570 let v: D38s12 = "1".parse().unwrap();
571 assert_eq!(v, D38s12::ONE);
572 let v: D38s12 = "1.0".parse().unwrap();
573 assert_eq!(v, D38s12::ONE);
574 }
575
576 #[test]
578 fn from_str_one_point_one_parses_exactly() {
579 let v: D38s12 = "1.1".parse().unwrap();
580 assert_eq!(v.to_bits(), 1_100_000_000_000);
581 }
582
583 #[test]
585 fn from_str_signs() {
586 let neg: D38s12 = "-1.5".parse().unwrap();
587 assert_eq!(neg.to_bits(), -1_500_000_000_000);
588
589 let pos: D38s12 = "+1.5".parse().unwrap();
590 assert_eq!(pos.to_bits(), 1_500_000_000_000);
591 }
592
593 #[test]
595 fn from_str_short_fractional_pads() {
596 let v: D38s12 = "0.5".parse().unwrap();
598 assert_eq!(v.to_bits(), 500_000_000_000);
599 }
600
601 #[test]
603 fn from_str_full_scale_fractional() {
604 let v: D38s12 = "1.500000000000".parse().unwrap();
605 assert_eq!(v.to_bits(), 1_500_000_000_000);
606 }
607
608 #[test]
611 fn from_str_empty_is_err() {
612 let r: Result<D38s12, _> = "".parse();
613 assert_eq!(r, Err(ParseError::Empty));
614 }
615
616 #[test]
617 fn from_str_sign_only_is_err() {
618 assert_eq!("-".parse::<D38s12>(), Err(ParseError::SignOnly));
619 assert_eq!("+".parse::<D38s12>(), Err(ParseError::SignOnly));
620 }
621
622 #[test]
623 fn from_str_leading_zero_is_err() {
624 assert_eq!("01".parse::<D38s12>(), Err(ParseError::LeadingZero));
625 assert_eq!(
626 "01.5".parse::<D38s12>(),
627 Err(ParseError::LeadingZero)
628 );
629 assert_eq!("00".parse::<D38s12>(), Err(ParseError::LeadingZero));
630 }
631
632 #[test]
633 fn from_str_overlong_fractional_is_err() {
634 let r: Result<D38s12, _> = "0.1234567890123".parse();
636 assert_eq!(r, Err(ParseError::OverlongFractional));
637 }
638
639 #[test]
640 fn from_str_scientific_notation_is_err() {
641 assert_eq!(
642 "1e3".parse::<D38s12>(),
643 Err(ParseError::ScientificNotation)
644 );
645 assert_eq!(
646 "1.5E2".parse::<D38s12>(),
647 Err(ParseError::ScientificNotation)
648 );
649 }
650
651 #[test]
652 fn from_str_invalid_char_is_err() {
653 assert_eq!(
654 "garbage".parse::<D38s12>(),
655 Err(ParseError::InvalidChar)
656 );
657 assert_eq!(
658 "1.2x".parse::<D38s12>(),
659 Err(ParseError::InvalidChar)
660 );
661 assert_eq!(
662 "1..2".parse::<D38s12>(),
663 Err(ParseError::InvalidChar)
664 );
665 }
666
667 #[test]
668 fn from_str_missing_digits_is_err() {
669 assert_eq!(
670 ".5".parse::<D38s12>(),
671 Err(ParseError::MissingDigits)
672 );
673 assert_eq!(
674 "5.".parse::<D38s12>(),
675 Err(ParseError::MissingDigits)
676 );
677 assert_eq!(
678 "-.5".parse::<D38s12>(),
679 Err(ParseError::MissingDigits)
680 );
681 }
682
683 #[test]
684 fn from_str_out_of_range_is_err() {
685 let r: Result<D38s12, _> = "1000000000000000000000000000".parse();
689 assert_eq!(r, Err(ParseError::OutOfRange));
690 }
691
692 #[test]
699 fn from_str_i128_min_boundary() {
700 let s = "-170141183460469231731687303.715884105728";
701 let v: D38s12 = s.parse().unwrap();
702 assert_eq!(v.to_bits(), i128::MIN);
703 }
704
705 #[test]
708 fn from_str_i128_max_boundary() {
709 let s = "170141183460469231731687303.715884105727";
710 let v: D38s12 = s.parse().unwrap();
711 assert_eq!(v.to_bits(), i128::MAX);
712 }
713
714 #[test]
716 fn from_str_just_above_max_overflows() {
717 let s = "170141183460469231731687303.715884105728";
719 let r: Result<D38s12, _> = s.parse();
720 assert_eq!(r, Err(ParseError::OutOfRange));
721 }
722
723 #[cfg(feature = "alloc")]
728 #[test]
729 fn round_trip_representative_values() {
730 let cases: &[i128] = &[
731 0,
732 1,
733 -1,
734 1_000_000_000_000, -1_000_000_000_000,
736 1_500_000_000_000, -1_500_000_000_000,
738 1_100_000_000_000, 2_200_000_000_000, 3_300_000_000_000, 1_234_567_890_123, -1_234_567_890_123,
745 4_567_891_234_567, 7_890_123_456_789, i128::MAX,
748 i128::MIN,
749 i128::MAX / 2,
750 i128::MIN / 2,
751 ];
752 for &raw in cases {
753 let v = D38s12::from_bits(raw);
754 let s = v.to_string();
755 let parsed: D38s12 = s.parse().unwrap_or_else(|e| {
756 panic!("round-trip parse failed for raw={raw}, s={s:?}, err={e:?}")
757 });
758 assert_eq!(
759 parsed.to_bits(),
760 raw,
761 "round-trip mismatch: raw={raw}, s={s:?}, parsed_bits={}",
762 parsed.to_bits()
763 );
764 }
765 }
766
767 #[cfg(feature = "alloc")]
770 #[test]
771 fn round_trip_other_scale() {
772 type D6 = D38<6>;
773 let cases: &[i128] = &[
774 0,
775 1,
776 -1,
777 1_000_000,
778 -1_000_000,
779 1_500_000,
780 i128::MAX,
781 i128::MIN,
782 ];
783 for &raw in cases {
784 let v = D6::from_bits(raw);
785 let s = v.to_string();
786 let parsed: D6 = s.parse().expect("round-trip parse");
787 assert_eq!(
788 parsed.to_bits(),
789 raw,
790 "round-trip mismatch at SCALE=6, raw={raw}"
791 );
792 }
793 }
794
795 #[cfg(feature = "alloc")]
798 #[test]
799 fn round_trip_scale_zero() {
800 type D0 = D38<0>;
801 let cases: &[i128] = &[0, 1, -1, 42, -42, i128::MAX, i128::MIN];
802 for &raw in cases {
803 let v = D0::from_bits(raw);
804 let s = v.to_string();
805 let parsed: D0 = s.parse().expect("round-trip parse");
806 assert_eq!(
807 parsed.to_bits(),
808 raw,
809 "round-trip mismatch at SCALE=0, raw={raw}"
810 );
811 }
812 }
813}