1use core::fmt;
6use core::str::FromStr;
7
8use crate::Decimal;
9use crate::ParseError;
10
11#[derive(Debug, Clone, PartialEq)]
21pub struct CompactDecimal {
22 significand: Decimal,
23 exponent: u8,
24}
25
26impl CompactDecimal {
27 pub fn from_significand_and_exponent(significand: Decimal, exponent: u8) -> Self {
29 Self {
30 significand,
31 exponent,
32 }
33 }
34
35 pub fn significand(&self) -> &Decimal {
47 &self.significand
48 }
49
50 pub fn into_significand(self) -> Decimal {
64 self.significand
65 }
66
67 pub fn exponent(&self) -> u8 {
76 self.exponent
77 }
78}
79
80impl writeable::Writeable for CompactDecimal {
97 fn write_to<W: fmt::Write + ?Sized>(&self, sink: &mut W) -> fmt::Result {
98 self.significand.write_to(sink)?;
99 if self.exponent != 0 {
100 sink.write_char('c')?;
101 self.exponent.write_to(sink)?;
102 }
103 Ok(())
104 }
105
106 fn writeable_length_hint(&self) -> writeable::LengthHint {
107 let mut result = self.significand.writeable_length_hint();
108 if self.exponent != 0 {
109 result += self.exponent.writeable_length_hint() + 1;
110 }
111 result
112 }
113}
114
115writeable::impl_display_with_writeable!(CompactDecimal);
116
117impl CompactDecimal {
118 #[inline]
119 pub fn try_from_str(s: &str) -> Result<Self, ParseError> {
121 Self::try_from_utf8(s.as_bytes())
122 }
123
124 fn try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError> {
126 if code_units.iter().any(|&c| c == b'e' || c == b'E') {
127 return Err(ParseError::Syntax);
128 }
129 let mut parts = code_units.split(|&c| c == b'c');
130 let significand = Decimal::try_from_utf8(parts.next().ok_or(ParseError::Syntax)?)?;
131 match parts.next() {
132 None => Ok(CompactDecimal {
133 significand,
134 exponent: 0,
135 }),
136 Some(exponent_str) => {
137 let exponent_str =
138 core::str::from_utf8(exponent_str).map_err(|_| ParseError::Syntax)?;
139 if parts.next().is_some() {
140 return Err(ParseError::Syntax);
141 }
142 if exponent_str.is_empty()
143 || exponent_str.bytes().next() == Some(b'0')
144 || !exponent_str.bytes().all(|c| c.is_ascii_digit())
145 {
146 return Err(ParseError::Syntax);
147 }
148 let exponent = exponent_str.parse().map_err(|_| ParseError::Limit)?;
149 Ok(CompactDecimal {
150 significand,
151 exponent,
152 })
153 }
154 }
155 }
156}
157
158impl FromStr for CompactDecimal {
159 type Err = ParseError;
160 fn from_str(s: &str) -> Result<Self, Self::Err> {
161 Self::try_from_str(s)
162 }
163}
164
165#[test]
166fn test_compact_syntax_error() {
167 #[derive(Debug)]
168 struct TestCase {
169 pub input_str: &'static str,
170 pub expected_err: Option<ParseError>,
171 }
172 let cases = [
173 TestCase {
174 input_str: "-123e4",
175 expected_err: Some(ParseError::Syntax),
176 },
177 TestCase {
178 input_str: "-123c",
179 expected_err: Some(ParseError::Syntax),
180 },
181 TestCase {
182 input_str: "1c10",
183 expected_err: None,
184 },
185 TestCase {
186 input_str: "1E1c1",
187 expected_err: Some(ParseError::Syntax),
188 },
189 TestCase {
190 input_str: "1e1c1",
191 expected_err: Some(ParseError::Syntax),
192 },
193 TestCase {
194 input_str: "1c1e1",
195 expected_err: Some(ParseError::Syntax),
196 },
197 TestCase {
198 input_str: "1c1E1",
199 expected_err: Some(ParseError::Syntax),
200 },
201 TestCase {
202 input_str: "-1c01",
203 expected_err: Some(ParseError::Syntax),
204 },
205 TestCase {
206 input_str: "-1c-1",
207 expected_err: Some(ParseError::Syntax),
208 },
209 TestCase {
210 input_str: "-1c1",
211 expected_err: None,
212 },
213 ];
214 for cas in &cases {
215 match CompactDecimal::from_str(cas.input_str) {
216 Ok(dec) => {
217 assert_eq!(cas.expected_err, None, "{cas:?}");
218 assert_eq!(cas.input_str, dec.to_string(), "{cas:?}");
219 }
220 Err(err) => {
221 assert_eq!(cas.expected_err, Some(err), "{cas:?}");
222 }
223 }
224 }
225}