1use rust_decimal::Decimal;
2
3#[macro_export]
8macro_rules! dec {
9 ($amount:expr) => {
10 const { $crate::decimal::decimal_from_str_const(stringify!($amount)) }
11 };
12}
13
14pub const fn decimal_from_str_const(s: &str) -> Decimal {
19 let mut bs = s.as_bytes();
20
21 let mut negative = false;
23 match bs.split_first() {
24 Some((b, rest)) => match b {
25 b'-' => {
26 negative = true;
27 bs = rest;
28 }
29 b'+' => bs = rest,
30 _ => {}
31 },
32 None => panic!("empty"),
33 }
34
35 let mut num_digits: u8 = 0;
38 let mut idx_point: Option<u8> = None;
39 let mut accum: u64 = 0;
40 while let [b, rest @ ..] = bs {
41 bs = rest;
42 match *b {
43 b'0'..=b'9' => {
44 let d = (*b - b'0') as u64;
45 accum = accum.checked_mul(10).expect("overflow");
46 accum = accum.checked_add(d).expect("overflow");
47 num_digits += 1;
48 }
49 b'.' => {
50 if idx_point.is_some() {
51 panic!("duplicate decimal point");
52 }
53 idx_point = Some(num_digits);
54 }
55 b'_' => continue,
56 _ => panic!("not a valid decimal"),
57 }
58 }
59
60 if num_digits == 0 {
62 panic!("no digits");
63 }
64
65 let lo = (accum & 0xffff_ffff) as u32;
66 let mid = ((accum >> 32) & 0xffff_ffff) as u32;
67 let hi = 0;
68 let idx_point = match idx_point {
69 Some(x) => x,
70 None => num_digits,
71 };
72 let scale = (num_digits - idx_point) as u32;
73
74 Decimal::from_parts(lo, mid, hi, negative, scale)
75}
76
77#[cfg(test)]
78mod test {
79 use proptest::proptest;
80
81 use super::*;
82
83 const MYCONST: Decimal = dec!(132.456);
84
85 #[test]
86 fn test_decimal_from_str_const() {
87 #[track_caller]
88 fn ok(s: &str) {
89 let actual = decimal_from_str_const(s);
90 let expected = Decimal::from_str_radix(s, 10).unwrap();
91 assert_eq!(actual, expected);
92 }
93
94 ok("1");
95 ok("1.");
96 ok(".1");
97 ok("-1");
98 ok("0.1");
99 ok("-0.1");
100 ok("2.0");
101 ok("-0");
102
103 ok("9223372036854775808");
104 ok("-9223372036854775808");
105
106 ok("9.223372036854775808");
107 ok("922337203685477580.8");
108 ok("9223372036854775808.");
109
110 assert_eq!(MYCONST, Decimal::from_parts(132456, 0, 0, false, 3));
111
112 proptest!(|(x: u64, negative: bool, scale in 0u32..20)| {
113 let x = x & 0x7fff_ffff_ffff_ffff;
115 let lo = (x & 0xffff_ffff) as u32;
116 let mid = ((x >> 32) & 0xffff_ffff) as u32;
117 let hi = 0;
118 let dec = Decimal::from_parts(lo, mid, hi, negative, scale);
119 ok(&dec.to_string());
120 })
121 }
122}