1use std::fmt;
9
10use serde::de::{self, Visitor};
11use serde::{Deserialize, Deserializer, Serialize, Serializer};
12
13pub const SCALE: u32 = 9;
15
16const ONE_UNITS: i128 = 1_000_000_000;
18
19#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
21pub struct Decimal {
22 units: i128,
23}
24
25#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
28pub enum Rounding {
29 HalfEven,
31 TowardZero,
33}
34
35#[derive(Clone, Copy, PartialEq, Eq, Debug)]
37pub enum DecimalError {
38 Empty,
40 Syntax { offset: usize },
43 TooPrecise,
45 OutOfRange,
47}
48
49impl fmt::Display for DecimalError {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 match self {
52 DecimalError::Empty => write!(f, "empty decimal"),
53 DecimalError::Syntax { offset } => {
54 write!(f, "malformed decimal at byte {offset}")
55 }
56 DecimalError::TooPrecise => {
57 write!(f, "decimal has more than {SCALE} fractional digits")
58 }
59 DecimalError::OutOfRange => write!(f, "decimal out of range"),
60 }
61 }
62}
63
64impl std::error::Error for DecimalError {}
65
66#[derive(Clone, Copy, PartialEq, Eq, Debug)]
68pub enum ArithError {
69 Overflow,
71 DivisionByZero,
73 InvalidScale,
75}
76
77impl fmt::Display for ArithError {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 match self {
80 ArithError::Overflow => write!(f, "decimal arithmetic overflow"),
81 ArithError::DivisionByZero => write!(f, "decimal division by zero"),
82 ArithError::InvalidScale => write!(f, "scale exceeds {SCALE}"),
83 }
84 }
85}
86
87impl std::error::Error for ArithError {}
88
89fn div_round(n: i128, d: i128, rounding: Rounding) -> Result<i128, ArithError> {
91 let q = n.checked_div(d).ok_or(ArithError::Overflow)?;
92 let rem = n.checked_rem(d).ok_or(ArithError::Overflow)?;
93 if rem == 0 || rounding == Rounding::TowardZero {
94 return Ok(q);
95 }
96 let twice_rem = rem.unsigned_abs() * 2;
98 let abs_d = d.unsigned_abs();
99 let away = twice_rem > abs_d || (twice_rem == abs_d && q % 2 != 0);
100 if !away {
101 return Ok(q);
102 }
103 if (n < 0) != (d < 0) {
104 q.checked_sub(1).ok_or(ArithError::Overflow)
105 } else {
106 q.checked_add(1).ok_or(ArithError::Overflow)
107 }
108}
109
110impl Decimal {
111 pub const ZERO: Decimal = Decimal { units: 0 };
113
114 pub const fn from_units(units: i128) -> Decimal {
116 Decimal { units }
117 }
118
119 pub const fn units(&self) -> i128 {
121 self.units
122 }
123
124 pub const fn from_i64(v: i64) -> Decimal {
126 Decimal {
127 units: v as i128 * ONE_UNITS,
128 }
129 }
130
131 pub const fn is_negative(&self) -> bool {
133 self.units < 0
134 }
135
136 pub fn parse(s: &str) -> Result<Decimal, DecimalError> {
144 let b = s.as_bytes();
145 if b.is_empty() {
146 return Err(DecimalError::Empty);
147 }
148 let mut i = 0;
149 let negative = b[0] == b'-';
150 if negative {
151 i = 1;
152 }
153 let int_start = i;
154 while i < b.len() && b[i].is_ascii_digit() {
155 i += 1;
156 }
157 let int_digits = &b[int_start..i];
158 if int_digits.is_empty() {
159 return Err(DecimalError::Syntax { offset: int_start });
160 }
161 if int_digits.len() > 1 && int_digits[0] == b'0' {
162 return Err(DecimalError::Syntax {
163 offset: int_start + 1,
164 });
165 }
166 let mut frac_digits: &[u8] = &[];
167 if i < b.len() {
168 if b[i] != b'.' {
169 return Err(DecimalError::Syntax { offset: i });
170 }
171 i += 1;
172 let frac_start = i;
173 while i < b.len() && b[i].is_ascii_digit() {
174 i += 1;
175 }
176 frac_digits = &b[frac_start..i];
177 if frac_digits.is_empty() {
178 return Err(DecimalError::Syntax { offset: frac_start });
179 }
180 if i < b.len() {
181 return Err(DecimalError::Syntax { offset: i });
182 }
183 if frac_digits.len() > SCALE as usize {
184 return Err(DecimalError::TooPrecise);
185 }
186 }
187
188 let mut mag: u128 = 0;
190 for &d in int_digits {
191 mag = mag
192 .checked_mul(10)
193 .and_then(|m| m.checked_add(u128::from(d - b'0')))
194 .ok_or(DecimalError::OutOfRange)?;
195 }
196 mag = mag
197 .checked_mul(ONE_UNITS as u128)
198 .ok_or(DecimalError::OutOfRange)?;
199 let mut frac: u128 = 0;
200 for &d in frac_digits {
201 frac = frac * 10 + u128::from(d - b'0');
202 }
203 frac *= 10u128.pow(SCALE - frac_digits.len() as u32);
204 mag = mag.checked_add(frac).ok_or(DecimalError::OutOfRange)?;
205
206 let limit = i128::MAX as u128;
207 let units = if negative {
208 if mag == limit + 1 {
209 i128::MIN
210 } else if mag > limit {
211 return Err(DecimalError::OutOfRange);
212 } else {
213 -(mag as i128)
214 }
215 } else if mag > limit {
216 return Err(DecimalError::OutOfRange);
217 } else {
218 mag as i128
219 };
220 Ok(Decimal { units })
221 }
222
223 pub fn to_canonical_string(&self) -> String {
226 let mag = self.units.unsigned_abs();
227 let int = mag / ONE_UNITS as u128;
228 let frac = mag % ONE_UNITS as u128;
229 let sign = if self.units < 0 { "-" } else { "" };
230 if frac == 0 {
231 format!("{sign}{int}")
232 } else {
233 let digits = format!("{frac:09}");
234 format!("{sign}{int}.{}", digits.trim_end_matches('0'))
235 }
236 }
237
238 pub fn checked_add(self, rhs: Decimal) -> Result<Decimal, ArithError> {
240 self.units
241 .checked_add(rhs.units)
242 .map(Decimal::from_units)
243 .ok_or(ArithError::Overflow)
244 }
245
246 pub fn checked_sub(self, rhs: Decimal) -> Result<Decimal, ArithError> {
248 self.units
249 .checked_sub(rhs.units)
250 .map(Decimal::from_units)
251 .ok_or(ArithError::Overflow)
252 }
253
254 pub fn checked_mul(self, rhs: Decimal, rounding: Rounding) -> Result<Decimal, ArithError> {
260 let p = self
261 .units
262 .checked_mul(rhs.units)
263 .ok_or(ArithError::Overflow)?;
264 div_round(p, ONE_UNITS, rounding).map(Decimal::from_units)
265 }
266
267 pub fn checked_div(self, rhs: Decimal, rounding: Rounding) -> Result<Decimal, ArithError> {
273 if rhs.units == 0 {
274 return Err(ArithError::DivisionByZero);
275 }
276 let n = self
277 .units
278 .checked_mul(ONE_UNITS)
279 .ok_or(ArithError::Overflow)?;
280 div_round(n, rhs.units, rounding).map(Decimal::from_units)
281 }
282
283 pub fn round_to_scale(self, scale: u32, rounding: Rounding) -> Result<Decimal, ArithError> {
287 if scale > SCALE {
288 return Err(ArithError::InvalidScale);
289 }
290 let factor = 10i128.pow(SCALE - scale);
291 let q = div_round(self.units, factor, rounding)?;
292 q.checked_mul(factor)
293 .map(Decimal::from_units)
294 .ok_or(ArithError::Overflow)
295 }
296
297 pub fn to_f64_nearest(&self) -> f64 {
300 self.to_canonical_string()
301 .parse::<f64>()
302 .expect("canonical decimal is valid f64 syntax")
303 }
304}
305
306impl fmt::Display for Decimal {
307 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
308 f.write_str(&self.to_canonical_string())
309 }
310}
311
312impl Serialize for Decimal {
313 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
314 serializer.serialize_str(&self.to_canonical_string())
315 }
316}
317
318impl schemars::JsonSchema for Decimal {
319 fn schema_name() -> std::borrow::Cow<'static, str> {
320 "Decimal".into()
321 }
322
323 fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
325 schemars::json_schema!({
326 "type": "string",
327 "pattern": "^-?(0|[1-9][0-9]*)(\\.[0-9]+)?$"
328 })
329 }
330}
331
332struct DecimalVisitor;
333
334impl Visitor<'_> for DecimalVisitor {
335 type Value = Decimal;
336
337 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
338 f.write_str("a decimal string")
339 }
340
341 fn visit_str<E: de::Error>(self, v: &str) -> Result<Decimal, E> {
342 Decimal::parse(v).map_err(E::custom)
343 }
344}
345
346impl<'de> Deserialize<'de> for Decimal {
347 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Decimal, D::Error> {
349 deserializer.deserialize_str(DecimalVisitor)
350 }
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356
357 fn d(s: &str) -> Decimal {
358 Decimal::parse(s).unwrap()
359 }
360
361 #[test]
362 fn grammar_accepts() {
363 let cases = [
364 ("0", 0),
365 ("-0", 0),
366 ("-0.0", 0),
367 ("1", ONE_UNITS),
368 ("1.5", 1_500_000_000),
369 ("1.50", 1_500_000_000),
370 ("-1.5", -1_500_000_000),
371 ("0.000000001", 1),
372 ("-0.000000001", -1),
373 ("10.000000000", 10 * ONE_UNITS),
374 ("123456789", 123_456_789 * ONE_UNITS),
375 ];
376 for (s, units) in cases {
377 assert_eq!(Decimal::parse(s), Ok(Decimal::from_units(units)), "{s}");
378 }
379 }
380
381 #[test]
382 fn grammar_refuses() {
383 use DecimalError::*;
384 let cases: [(&str, DecimalError); 16] = [
385 ("", Empty),
386 ("+1", Syntax { offset: 0 }),
387 ("-", Syntax { offset: 1 }),
388 ("01", Syntax { offset: 1 }),
389 ("-01", Syntax { offset: 2 }),
390 ("00", Syntax { offset: 1 }),
391 (".", Syntax { offset: 0 }),
392 (".5", Syntax { offset: 0 }),
393 ("1.", Syntax { offset: 2 }),
394 ("1e3", Syntax { offset: 1 }),
395 ("1.0e3", Syntax { offset: 3 }),
396 (" 1", Syntax { offset: 0 }),
397 ("1 ", Syntax { offset: 1 }),
398 ("1.2.3", Syntax { offset: 3 }),
399 ("0.0000000001", TooPrecise),
400 ("1.0000000000", TooPrecise),
401 ];
402 for (s, e) in cases {
403 assert_eq!(Decimal::parse(s), Err(e), "{s:?}");
404 }
405 assert_eq!(Decimal::parse("--1"), Err(Syntax { offset: 1 }));
406 assert_eq!(Decimal::parse("NaN"), Err(Syntax { offset: 0 }));
407 assert_eq!(Decimal::parse("١"), Err(Syntax { offset: 0 }));
408 }
409
410 #[test]
411 fn range_boundaries() {
412 let max = Decimal::from_units(i128::MAX);
413 let min = Decimal::from_units(i128::MIN);
414 assert_eq!(
415 max.to_canonical_string(),
416 "170141183460469231731687303715.884105727"
417 );
418 assert_eq!(
419 min.to_canonical_string(),
420 "-170141183460469231731687303715.884105728"
421 );
422 assert_eq!(d("170141183460469231731687303715.884105727"), max);
423 assert_eq!(d("-170141183460469231731687303715.884105728"), min);
424 assert_eq!(
425 Decimal::parse("170141183460469231731687303715.884105728"),
426 Err(DecimalError::OutOfRange)
427 );
428 assert_eq!(
429 Decimal::parse("-170141183460469231731687303715.884105729"),
430 Err(DecimalError::OutOfRange)
431 );
432 assert_eq!(
433 Decimal::parse("999999999999999999999999999999999999999999"),
434 Err(DecimalError::OutOfRange)
435 );
436 }
437
438 #[test]
439 fn canonical_round_trip() {
440 let units = [
441 0,
442 1,
443 -1,
444 ONE_UNITS,
445 -ONE_UNITS,
446 1_500_000_000,
447 -1_050_000_000,
448 123_456_789_012,
449 i128::MAX,
450 i128::MIN,
451 i128::MAX - 1,
452 i128::MIN + 1,
453 ];
454 for u in units {
455 let x = Decimal::from_units(u);
456 let s = x.to_canonical_string();
457 assert_eq!(d(&s), x, "{s}");
458 assert_eq!(x.to_string(), s);
459 assert!(!s.ends_with('.'));
460 assert!(!s.contains('.') || !s.ends_with('0'), "{s}");
461 }
462 assert_eq!(d("-0").to_canonical_string(), "0");
463 assert_eq!(d("1.50").to_canonical_string(), "1.5");
464 assert_eq!(d("2.000").to_canonical_string(), "2");
465 assert_eq!(d("-0.05").to_canonical_string(), "-0.05");
466 }
467
468 #[test]
469 fn from_i64_and_sign() {
470 assert_eq!(
471 Decimal::from_i64(i64::MIN).units(),
472 i64::MIN as i128 * ONE_UNITS
473 );
474 assert_eq!(Decimal::from_i64(-3), d("-3"));
475 assert!(d("-0.1").is_negative());
476 assert!(!Decimal::ZERO.is_negative());
477 assert!(!d("-0").is_negative());
478 }
479
480 #[test]
481 fn add_sub_exact_and_overflow() {
482 assert_eq!(d("0.1").checked_add(d("0.2")), Ok(d("0.3")));
483 assert_eq!(d("0.1").checked_sub(d("0.3")), Ok(d("-0.2")));
484 let max = Decimal::from_units(i128::MAX);
485 let min = Decimal::from_units(i128::MIN);
486 assert_eq!(
487 max.checked_add(Decimal::from_units(1)),
488 Err(ArithError::Overflow)
489 );
490 assert_eq!(
491 min.checked_sub(Decimal::from_units(1)),
492 Err(ArithError::Overflow)
493 );
494 assert_eq!(max.checked_add(Decimal::ZERO), Ok(max));
495 }
496
497 #[test]
498 fn mul_rounding() {
499 use Rounding::*;
500 assert_eq!(d("1.5").checked_mul(d("2"), HalfEven), Ok(d("3")));
501 assert_eq!(d("-1.5").checked_mul(d("2"), HalfEven), Ok(d("-3")));
502 assert_eq!(
504 d("0.000000025").checked_mul(d("0.1"), HalfEven),
505 Ok(d("0.000000002"))
506 );
507 assert_eq!(
509 d("0.000000035").checked_mul(d("0.1"), HalfEven),
510 Ok(d("0.000000004"))
511 );
512 assert_eq!(
513 d("-0.000000025").checked_mul(d("0.1"), HalfEven),
514 Ok(d("-0.000000002"))
515 );
516 assert_eq!(
517 d("-0.000000035").checked_mul(d("0.1"), HalfEven),
518 Ok(d("-0.000000004"))
519 );
520 assert_eq!(
522 d("0.000000026").checked_mul(d("0.1"), HalfEven),
523 Ok(d("0.000000003"))
524 );
525 assert_eq!(
526 d("0.000000029").checked_mul(d("0.1"), TowardZero),
527 Ok(d("0.000000002"))
528 );
529 assert_eq!(
530 d("-0.000000029").checked_mul(d("0.1"), TowardZero),
531 Ok(d("-0.000000002"))
532 );
533 }
534
535 #[test]
536 fn mul_overflow_is_declared_intermediate() {
537 let big = d("10000000000000");
538 assert_eq!(
540 big.checked_mul(big, Rounding::HalfEven),
541 Err(ArithError::Overflow)
542 );
543 assert_eq!(
544 d("1000000").checked_mul(d("1000000"), Rounding::HalfEven),
545 Ok(d("1000000000000"))
546 );
547 }
548
549 #[test]
550 fn div_rounding_and_errors() {
551 use Rounding::*;
552 assert_eq!(d("1").checked_div(d("4"), HalfEven), Ok(d("0.25")));
553 assert_eq!(d("1").checked_div(d("3"), HalfEven), Ok(d("0.333333333")));
554 assert_eq!(d("2").checked_div(d("3"), HalfEven), Ok(d("0.666666667")));
555 assert_eq!(d("2").checked_div(d("3"), TowardZero), Ok(d("0.666666666")));
556 assert_eq!(d("-2").checked_div(d("3"), HalfEven), Ok(d("-0.666666667")));
557 assert_eq!(d("2").checked_div(d("-3"), HalfEven), Ok(d("-0.666666667")));
558 assert_eq!(d("-2").checked_div(d("-3"), HalfEven), Ok(d("0.666666667")));
559 assert_eq!(
561 d("0.000000005").checked_div(d("2"), HalfEven),
562 Ok(d("0.000000002"))
563 );
564 assert_eq!(
565 d("-0.000000005").checked_div(d("2"), HalfEven),
566 Ok(d("-0.000000002"))
567 );
568 assert_eq!(
569 d("0.000000007").checked_div(d("2"), HalfEven),
570 Ok(d("0.000000004"))
571 );
572 assert_eq!(
573 d("1").checked_div(Decimal::ZERO, HalfEven),
574 Err(ArithError::DivisionByZero)
575 );
576 assert_eq!(
577 Decimal::from_units(i128::MAX).checked_div(d("1"), HalfEven),
578 Err(ArithError::Overflow)
579 );
580 }
581
582 #[test]
583 fn round_to_scale_cases() {
584 use Rounding::*;
585 assert_eq!(d("2.5").round_to_scale(0, HalfEven), Ok(d("2")));
586 assert_eq!(d("3.5").round_to_scale(0, HalfEven), Ok(d("4")));
587 assert_eq!(d("-2.5").round_to_scale(0, HalfEven), Ok(d("-2")));
588 assert_eq!(d("-3.5").round_to_scale(0, HalfEven), Ok(d("-4")));
589 assert_eq!(d("2.51").round_to_scale(0, HalfEven), Ok(d("3")));
590 assert_eq!(d("2.9").round_to_scale(0, TowardZero), Ok(d("2")));
591 assert_eq!(d("-2.9").round_to_scale(0, TowardZero), Ok(d("-2")));
592 assert_eq!(d("1.005").round_to_scale(2, HalfEven), Ok(d("1")));
593 assert_eq!(d("1.015").round_to_scale(2, HalfEven), Ok(d("1.02")));
594 assert_eq!(
595 d("1.123456789").round_to_scale(9, HalfEven),
596 Ok(d("1.123456789"))
597 );
598 assert_eq!(
599 d("1").round_to_scale(10, HalfEven),
600 Err(ArithError::InvalidScale)
601 );
602 assert_eq!(
603 Decimal::from_units(i128::MAX).round_to_scale(0, HalfEven),
604 Err(ArithError::Overflow)
605 );
606 }
607
608 #[test]
609 fn nearest_f64() {
610 assert_eq!(d("0.1").to_f64_nearest(), 0.1);
611 assert_eq!(d("-2.5").to_f64_nearest(), -2.5);
612 assert_eq!(Decimal::ZERO.to_f64_nearest(), 0.0);
613 assert!(Decimal::from_units(i128::MIN).to_f64_nearest().is_finite());
614 }
615
616 #[test]
617 fn serde_string_only() {
618 assert_eq!(serde_json::to_string(&d("1.50")).unwrap(), "\"1.5\"");
619 assert_eq!(
620 serde_json::from_str::<Decimal>("\"-0.25\"").unwrap(),
621 d("-0.25")
622 );
623 assert!(serde_json::from_str::<Decimal>("1.5").is_err());
624 assert!(serde_json::from_str::<Decimal>("1").is_err());
625 assert!(serde_json::from_str::<Decimal>("\"1e3\"").is_err());
626 assert!(serde_json::from_str::<Decimal>("\"0.0000000001\"").is_err());
627 assert!(serde_json::from_str::<Decimal>("null").is_err());
628 }
629}