icydb_schema/decimal/
text.rs1use crate::decimal::{Decimal, ParseDecimalError, ParseDecimalErrorReason};
2use std::{
3 fmt::{Display, Formatter},
4 str::FromStr,
5};
6
7impl Display for Decimal {
8 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
9 let (mantissa, scale) = self.normalized_parts();
10
11 if mantissa == 0 {
12 return f.write_str("0");
13 }
14
15 let negative = mantissa.is_negative();
16 let mut digits = mantissa.unsigned_abs().to_string();
17
18 if scale == 0 {
19 if negative {
20 return write!(f, "-{digits}");
21 }
22
23 return f.write_str(&digits);
24 }
25
26 let scale_usize = usize::try_from(scale).map_err(|_| std::fmt::Error)?;
27
28 if digits.len() <= scale_usize {
29 let zeros = "0".repeat(scale_usize - digits.len());
30 let body = format!("0.{zeros}{digits}");
31 if negative {
32 write!(f, "-{body}")
33 } else {
34 f.write_str(&body)
35 }
36 } else {
37 let split = digits.len() - scale_usize;
38 let frac = digits.split_off(split);
39 if negative {
40 write!(f, "-{digits}.{frac}")
41 } else {
42 write!(f, "{digits}.{frac}")
43 }
44 }
45 }
46}
47
48impl FromStr for Decimal {
49 type Err = ParseDecimalError;
50
51 fn from_str(s: &str) -> Result<Self, Self::Err> {
52 let input = s.trim();
54 if input.is_empty() {
55 return Err(ParseDecimalError::new(ParseDecimalErrorReason::Empty));
56 }
57
58 let (negative, unsigned) = if let Some(rest) = input.strip_prefix('-') {
59 (true, rest)
60 } else if let Some(rest) = input.strip_prefix('+') {
61 (false, rest)
62 } else {
63 (false, input)
64 };
65
66 if unsigned.contains(['e', 'E']) {
69 return Err(ParseDecimalError::new(
70 ParseDecimalErrorReason::ExponentNotationUnsupported,
71 ));
72 }
73
74 let (int_digits, frac_digits) = split_decimal_significand(unsigned)?;
76 let combined = format!("{int_digits}{frac_digits}");
77 let combined = strip_leading_zeros(&combined);
78
79 let scale_i64 = i64::try_from(frac_digits.len()).map_err(|_| {
80 ParseDecimalError::new(ParseDecimalErrorReason::FractionalLengthOverflow)
81 })?;
82 let digits = combined.to_string();
83
84 let scale = u32::try_from(scale_i64)
85 .map_err(|_| ParseDecimalError::new(ParseDecimalErrorReason::ScaleOverflow))?;
86
87 let signed_digits = if negative {
89 format!("-{digits}")
90 } else {
91 digits
92 };
93 let mantissa = signed_digits
94 .parse::<i128>()
95 .map_err(|_| ParseDecimalError::new(ParseDecimalErrorReason::MantissaOverflow))?;
96
97 Self::checked_from_mantissa_scale(mantissa, scale).ok_or_else(|| {
98 ParseDecimalError::new(ParseDecimalErrorReason::ScaleExceedsSupportedRange)
99 })
100 }
101}
102
103fn split_decimal_significand(input: &str) -> Result<(&str, &str), ParseDecimalError> {
104 let mut segments = input.split('.');
105 let int_digits = segments
106 .next()
107 .ok_or_else(|| ParseDecimalError::new(ParseDecimalErrorReason::InvalidSignificand))?;
108 let frac_digits = segments.next().unwrap_or("");
109
110 if segments.next().is_some() {
111 return Err(ParseDecimalError::new(
112 ParseDecimalErrorReason::InvalidSignificand,
113 ));
114 }
115
116 if int_digits.is_empty() && frac_digits.is_empty() {
117 return Err(ParseDecimalError::new(
118 ParseDecimalErrorReason::InvalidSignificand,
119 ));
120 }
121
122 if !int_digits.chars().all(|c| c.is_ascii_digit()) {
123 return Err(ParseDecimalError::new(
124 ParseDecimalErrorReason::InvalidDigits,
125 ));
126 }
127
128 if !frac_digits.chars().all(|c| c.is_ascii_digit()) {
129 return Err(ParseDecimalError::new(
130 ParseDecimalErrorReason::InvalidDigits,
131 ));
132 }
133
134 Ok((int_digits, frac_digits))
135}
136
137fn strip_leading_zeros(digits: &str) -> &str {
138 let trimmed = digits.trim_start_matches('0');
139 if trimmed.is_empty() { "0" } else { trimmed }
140}