Skip to main content

competitive/num/
decimal.rs

1use super::{One, Scan, ScanSource, Zero};
2use std::{cmp::Ordering, ops::Neg};
3
4pub mod addsub;
5pub mod convert;
6
7#[derive(PartialEq, PartialOrd, Eq, Ord, Copy, Clone, Debug, Hash)]
8enum Sign {
9    Minus,
10    Zero,
11    Plus,
12}
13
14impl Neg for Sign {
15    type Output = Self;
16
17    fn neg(self) -> Self::Output {
18        match self {
19            Sign::Minus => Sign::Plus,
20            Sign::Zero => Sign::Zero,
21            Sign::Plus => Sign::Minus,
22        }
23    }
24}
25
26const ZERO: Decimal = Decimal {
27    sign: Sign::Zero,
28    integer: Vec::new(),
29    decimal: Vec::new(),
30};
31
32const POW10: [u64; RADIX_LEN + 1] = [
33    1,
34    10,
35    100,
36    1_000,
37    10_000,
38    100_000,
39    1_000_000,
40    10_000_000,
41    100_000_000,
42    1_000_000_000,
43    10_000_000_000,
44    100_000_000_000,
45    1_000_000_000_000,
46    10_000_000_000_000,
47    100_000_000_000_000,
48    1_000_000_000_000_000,
49    10_000_000_000_000_000,
50    100_000_000_000_000_000,
51    1_000_000_000_000_000_000,
52];
53
54const RADIX: u64 = POW10[RADIX_LEN];
55const RADIX_LEN: usize = 18;
56
57#[derive(Clone, Debug, PartialEq, Eq, Hash)]
58pub struct Decimal {
59    sign: Sign,
60    integer: Vec<u64>,
61    decimal: Vec<u64>,
62}
63
64impl Default for Decimal {
65    fn default() -> Self {
66        Decimal::zero()
67    }
68}
69
70impl Zero for Decimal {
71    fn zero() -> Self {
72        ZERO
73    }
74
75    fn is_zero(&self) -> bool {
76        self.sign == Sign::Zero
77    }
78}
79
80impl One for Decimal {
81    fn one() -> Self {
82        Decimal {
83            sign: Sign::Plus,
84            integer: vec![1],
85            decimal: Vec::new(),
86        }
87    }
88}
89
90impl PartialOrd for Decimal {
91    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
92        Some(self.cmp(other))
93    }
94}
95
96impl Ord for Decimal {
97    fn cmp(&self, other: &Self) -> Ordering {
98        self.sign.cmp(&other.sign).then_with(|| match self.sign {
99            Sign::Minus => other.cmp_absolute_parts(self),
100            Sign::Zero => Ordering::Equal,
101            Sign::Plus => self.cmp_absolute_parts(other),
102        })
103    }
104}
105
106impl Neg for Decimal {
107    type Output = Self;
108
109    fn neg(self) -> Self::Output {
110        Self {
111            sign: -self.sign,
112            integer: self.integer,
113            decimal: self.decimal,
114        }
115    }
116}
117
118impl Decimal {
119    fn cmp_absolute_parts(&self, other: &Self) -> Ordering {
120        self.integer
121            .len()
122            .cmp(&other.integer.len())
123            .then_with(|| self.integer.iter().rev().cmp(other.integer.iter().rev()))
124            .then_with(|| self.decimal.iter().cmp(other.decimal.iter()))
125    }
126    fn normalize(&mut self) {
127        if let Some(&0) = self.decimal.last() {
128            let len = self
129                .decimal
130                .iter()
131                .rposition(|&d| d != 0)
132                .map_or(0, |i| i + 1);
133            self.decimal.truncate(len);
134        }
135        if self.decimal.len() < self.decimal.capacity() / 4 {
136            self.decimal.shrink_to_fit();
137        }
138        if let Some(&0) = self.integer.last() {
139            let len = self
140                .integer
141                .iter()
142                .rposition(|&d| d != 0)
143                .map_or(0, |i| i + 1);
144            self.integer.truncate(len);
145        }
146        if self.integer.len() < self.integer.capacity() / 4 {
147            self.integer.shrink_to_fit();
148        }
149        if self.integer.is_empty() && self.decimal.is_empty() {
150            self.sign = Sign::Zero;
151        }
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158    use crate::tools::Xorshift;
159    use crate::tools::testutil::integer_boundary_values;
160
161    #[test]
162    fn test_decimal_arithmetic() {
163        let mut rng = Xorshift::default();
164        let mut cases: Vec<_> = (-10..=10)
165            .flat_map(|a| (-10..=10).flat_map(move |b| (-4..=4).map(move |scale| (a, b, scale))))
166            .collect();
167        for _ in 0..10_000 {
168            let digits = rng.random(0..=35u32);
169            let bound = 10i128.pow(digits);
170            let a = rng.random(-bound..=bound);
171            let b = match rng.random(0..4) {
172                0 => a,
173                1 => -a,
174                _ => rng.random(-bound..=bound),
175            };
176            let scale = rng.random(-80..=80i32);
177            cases.push((a, b, scale));
178        }
179        for (a, b, scale) in cases {
180            let format = |x: i128| {
181                if x == 0 {
182                    return "0".to_owned();
183                }
184                let mut s = x.abs().to_string();
185                if scale > 0 {
186                    let places = scale as usize;
187                    if s.len() <= places {
188                        s = "0".repeat(places + 1 - s.len()) + &s;
189                    }
190                    s.insert(s.len() - places, '.');
191                    s = s.trim_end_matches('0').trim_end_matches('.').to_owned();
192                } else {
193                    s.push_str(&"0".repeat((-scale) as usize));
194                }
195                if x < 0 {
196                    s.insert(0, '-');
197                }
198                s
199            };
200            let x: Decimal = format(a).parse().unwrap();
201            let y: Decimal = format(b).parse().unwrap();
202            assert_eq!(x.to_string(), format(a));
203            assert_eq!(x.cmp(&y), a.cmp(&b));
204            assert_eq!(x.partial_cmp(&y), Some(a.cmp(&b)));
205            assert_eq!((-x.clone()).to_string(), format(-a));
206            assert_eq!((x.clone() + y.clone()).to_string(), format(a + b));
207            assert_eq!((&x + &y).to_string(), format(a + b));
208            assert_eq!((x.clone() - y.clone()).to_string(), format(a - b));
209            assert_eq!((&x - &y).to_string(), format(a - b));
210            let mut z = x.clone();
211            z += &y;
212            assert_eq!(z.to_string(), format(a + b));
213            z -= &y;
214            assert_eq!(z, x);
215            assert_eq!(x.is_zero(), a == 0);
216        }
217    }
218
219    #[test]
220    fn test_decimal_conversion() {
221        let mut rng = Xorshift::default();
222        macro_rules! check_integer {
223            ($($t:ty),*) => {$(
224                for x in integer_boundary_values!($t).into_iter().chain(rng.random_iter(..).take(1000)) {
225                    let decimal = Decimal::from(x);
226                    assert_eq!(decimal.to_string(), x.to_string());
227                    assert_eq!(x.to_string().parse::<Decimal>().unwrap(), decimal);
228                }
229            )*};
230        }
231        check_integer!(
232            u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize
233        );
234        for _ in 0..1000 {
235            let x = rng.random(-1_000_000..=1_000_000) as f64 / 10f64.powi(rng.random(0..=12));
236            assert_eq!(Decimal::from(x).to_string(), x.to_string());
237            let x = x as f32;
238            assert_eq!(Decimal::from(x).to_string(), x.to_string());
239            let n = rng.random(0..=100);
240            let digits: String = rng
241                .random_iter(b'0'..=b'9')
242                .take(n)
243                .map(char::from)
244                .collect();
245            let sign = if rng.random(0..2) == 0 { "+" } else { "-" };
246            let s = format!("{}00{}.{}00", sign, digits, digits);
247            let decimal: Decimal = s.parse().unwrap();
248            assert_eq!(decimal.to_string().parse::<Decimal>().unwrap(), decimal);
249            let mut invalid = s;
250            invalid.insert(
251                rng.random(0..=invalid.len()),
252                char::from(rng.random(b'a'..=b'z')),
253            );
254            assert!(invalid.parse::<Decimal>().is_err(), "{invalid}");
255        }
256    }
257}