1use alloc::format;
2use alloc::string::String;
3use alloc::string::ToString;
4use core::fmt;
5use core::str::FromStr;
6
7use crate::error::Error;
8use crate::event::Atom;
9use crate::ext::known::{WellKnown, impl_well_known, invalid};
10use crate::ext::{BigInt, Extension, Number};
11
12#[derive(Clone, PartialEq, Eq, Hash)]
36pub struct Decimal(String);
37
38impl Decimal {
39 pub fn new<S: Into<String>>(value: S) -> Result<Decimal, Error> {
43 let value = value.into();
44 if is_valid_decimal(&value) {
45 Ok(Decimal(value))
46 } else {
47 Err(invalid("invalid decimal"))
48 }
49 }
50
51 pub fn from_parts(mantissa: &BigInt, exponent: i64) -> Decimal {
55 let mut digits = mantissa.to_string();
56 let negative = digits.starts_with('-');
57 if negative {
58 digits.remove(0);
59 }
60 let mut rv = String::new();
61 if negative {
62 rv.push('-');
63 }
64 let scale = exponent.unsigned_abs();
65 if exponent < 0 && scale <= digits.len() as u64 + 20 {
66 let scale = scale as usize;
67 if digits.len() > scale {
68 rv.push_str(&digits[..digits.len() - scale]);
69 rv.push('.');
70 rv.push_str(&digits[digits.len() - scale..]);
71 } else {
72 rv.push_str("0.");
73 rv.extend(core::iter::repeat_n('0', scale - digits.len()));
74 rv.push_str(&digits);
75 }
76 } else {
77 rv.push_str(&digits);
78 if exponent != 0 {
79 rv.push('e');
80 rv.push_str(&exponent.to_string());
81 }
82 }
83 Decimal(rv)
84 }
85
86 pub fn to_parts(&self) -> (BigInt, i64) {
91 let s = self.0.as_str();
92 let (mantissa, exponent) = match s.find(['e', 'E']) {
93 Some(idx) => (&s[..idx], &s[idx + 1..]),
94 None => (s, "0"),
95 };
96 let exponent: i64 =
97 exponent
98 .trim_start_matches('+')
99 .parse()
100 .unwrap_or(if exponent.starts_with('-') {
101 i64::MIN
102 } else {
103 i64::MAX
104 });
105 let (digits, scale) = match mantissa.split_once('.') {
106 Some((int, frac)) => (format!("{}{}", int, frac), frac.len() as i64),
107 None => (mantissa.to_string(), 0),
108 };
109 let mantissa: BigInt = digits.parse().unwrap();
111 (mantissa, exponent.saturating_sub(scale))
112 }
113
114 pub fn as_str(&self) -> &str {
116 &self.0
117 }
118
119 pub fn into_string(self) -> String {
121 self.0
122 }
123}
124
125fn is_valid_decimal(s: &str) -> bool {
127 crate::ext::number::is_json_number(s)
128}
129
130impl fmt::Display for Decimal {
131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132 f.write_str(&self.0)
133 }
134}
135
136impl fmt::Debug for Decimal {
137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 write!(f, "Decimal({})", self.0)
139 }
140}
141
142impl FromStr for Decimal {
143 type Err = Error;
144
145 fn from_str(s: &str) -> Result<Decimal, Error> {
146 Decimal::new(s)
147 }
148}
149
150impl From<BigInt> for Decimal {
151 fn from(value: BigInt) -> Decimal {
152 Decimal(value.to_string())
153 }
154}
155
156impl From<i64> for Decimal {
157 fn from(value: i64) -> Decimal {
158 Decimal(value.to_string())
159 }
160}
161
162impl From<u64> for Decimal {
163 fn from(value: u64) -> Decimal {
164 Decimal(value.to_string())
165 }
166}
167
168impl TryFrom<f64> for Decimal {
169 type Error = Error;
170
171 fn try_from(value: f64) -> Result<Decimal, Error> {
173 if value.is_finite() {
174 Decimal::new(format!("{:?}", value))
175 } else {
176 Err(invalid("decimals cannot be infinite or NaN"))
177 }
178 }
179}
180
181impl TryFrom<f32> for Decimal {
182 type Error = Error;
183
184 fn try_from(value: f32) -> Result<Decimal, Error> {
187 if value.is_finite() {
188 Decimal::new(format!("{:?}", value))
189 } else {
190 Err(invalid("decimals cannot be infinite or NaN"))
191 }
192 }
193}
194
195impl Extension for Decimal {
196 fn name(&self) -> &str {
197 "decimal"
198 }
199
200 fn fallback(&self) -> Atom<'_> {
201 Atom::Str(self.0.as_str().into())
202 }
203}
204
205impl WellKnown for Decimal {
206 const EXPECTING: &'static str = "decimal";
207
208 fn from_atom(atom: &Atom) -> Result<Option<Decimal>, Error> {
210 Ok(Some(match *atom {
211 Atom::Ext(ref ext) => {
212 if let Some(value) = ext.downcast_ref::<Decimal>() {
213 value.clone()
214 } else if let Some(value) = ext.downcast_value_ref::<Number>() {
215 Decimal(value.as_str().to_string())
217 } else if let Some(value) = BigInt::from_atom(atom)? {
218 Decimal::from(value)
219 } else {
220 return Ok(None);
221 }
222 }
223 Atom::Str(ref value) => value.parse()?,
224 Atom::U64(value) => Decimal::from(value),
225 Atom::I64(value) => Decimal::from(value),
226 Atom::F64(value) => Decimal::try_from(value)?,
227 Atom::F32(value) => Decimal::try_from(value)?,
228 _ => return Ok(None),
229 }))
230 }
231}
232
233impl_well_known!(Decimal);
234
235#[test]
236fn test_decimal() {
237 for valid in ["0", "-0", "1.5", "-12.50", "1e5", "1E+5", "1.5e-7", "0.001"] {
238 assert_eq!(valid.parse::<Decimal>().unwrap().as_str(), valid);
239 }
240 for invalid in [
241 "", "-", "01", "1.", ".5", "+1", "1e", "1e+", "NaN", "1.5.5", " 1",
242 ] {
243 assert!(invalid.parse::<Decimal>().is_err(), "{}", invalid);
244 }
245 let parts = |s: &str| {
246 let (mantissa, exponent) = s.parse::<Decimal>().unwrap().to_parts();
247 (mantissa.to_string(), exponent)
248 };
249 assert_eq!(parts("12.50"), ("1250".into(), -2));
250 assert_eq!(parts("-0.001"), ("-1".into(), -3));
251 assert_eq!(parts("1.5e10"), ("15".into(), 9));
252 assert_eq!(parts("7"), ("7".into(), 0));
253 let from_parts = |m: i64, e: i64| Decimal::from_parts(&BigInt::from(m), e).into_string();
254 assert_eq!(from_parts(1250, -2), "12.50");
255 assert_eq!(from_parts(-1, -3), "-0.001");
256 assert_eq!(from_parts(15, 9), "15e9");
257 assert_eq!(from_parts(7, 0), "7");
258 assert_eq!(from_parts(1, -40), "1e-40");
259 assert_eq!(Decimal::try_from(0.1).unwrap().as_str(), "0.1");
260 assert_eq!(Decimal::try_from(1e100).unwrap().as_str(), "1e100");
261 assert!(Decimal::try_from(f64::NAN).is_err());
262}