fxd/
decimal.rs

1use crate::utils::get_units;
2use std::hash::{Hash, Hasher};
3use crate::error::{Result, Error};
4
5pub const MAX_UNITS: usize = 9;
6pub const MAX_FRAC: usize = 30;
7pub const MAX_DIGITS: usize = 65;
8pub const DIGITS_PER_UNIT: usize = 9;
9pub const UNIT: i32 = 1_000_000_000;
10pub const HALF_UNIT: i32 = UNIT / 2;
11pub const MAX_FRAC_UNITS: usize = (MAX_FRAC + DIGITS_PER_UNIT - 1) / DIGITS_PER_UNIT;
12pub const MAX_I64: FixedDecimal = FixedDecimal {
13    intg: 19 as i8,
14    frac: 0,
15    lsu: [854775807, 223372036, 9, 0, 0, 0, 0, 0, 0],
16};
17pub const MIN_I64: FixedDecimal = FixedDecimal {
18    intg: (19 | 0x80) as i8,
19    frac: 0,
20    lsu: [854775808, 223372036, 9, 0, 0, 0, 0, 0, 0],
21};
22pub const MAX_U64: FixedDecimal = FixedDecimal {
23    intg: 20 as i8,
24    frac: 0,
25    lsu: [709551615, 446744073, 18, 0, 0, 0, 0, 0, 0],
26};
27
28/// FixedDecimal is an implementation of "exact number" type defined
29/// SQL standard.
30/// It directly maps to MySQL data type "DECIMAL".
31/// it has fixed allocation: 9*4+2=38 bytes.
32#[derive(Debug, Clone)]
33pub struct FixedDecimal {
34    // Integral digits.
35    // Maximum is 65, if frac is 0.
36    // Use most significant bit to indicates whether it is negative
37    pub(crate) intg: i8,
38    // Fractional digits.
39    // Maximum is 30.
40    pub(crate) frac: i8,
41    pub(crate) lsu: [i32; MAX_UNITS],
42}
43
44impl FixedDecimal {
45    #[inline]
46    pub fn zero() -> FixedDecimal {
47        FixedDecimal {
48            intg: 1,
49            frac: 0,
50            lsu: [0; MAX_UNITS],
51        }
52    }
53
54    #[inline]
55    pub fn one() -> FixedDecimal {
56        let mut fd = Self::zero();
57        fd.lsu[0] = 1;
58        fd
59    }
60
61    #[inline]
62    pub fn is_neg(&self) -> bool {
63        self.intg & (0x80u8 as i8) != 0
64    }
65
66    #[inline]
67    pub fn set_neg(&mut self) {
68        self.intg |= 0x80u8 as i8;
69    }
70
71    #[inline]
72    pub(crate) fn set_neg_and_check_zero(&mut self) {
73        self.set_neg();
74        if self.is_zero() {
75            self.set_pos();
76        }
77    }
78
79    #[inline]
80    pub fn set_pos(&mut self) {
81        self.intg &= 0x7f;
82    }
83
84    #[inline]
85    pub fn set_zero(&mut self) {
86        self.intg = 1;
87        self.frac = 0;
88        self.reset_units();
89    }
90
91    #[inline]
92    pub fn is_zero(&self) -> bool {
93        self.lsu.iter().all(|n| *n == 0)
94    }
95
96    #[inline]
97    pub fn set_one(&mut self) {
98        self.intg = 1;
99        self.frac = 0;
100        self.reset_units();
101        self.lsu[0] = 1;
102    }
103
104    #[inline]
105    pub fn intg(&self) -> i8 {
106        self.intg & 0x7f
107    }
108
109    #[inline]
110    pub fn intg_units(&self) -> usize {
111        get_units(self.intg())
112    }
113
114    #[inline]
115    pub fn frac(&self) -> i8 {
116        self.frac
117    }
118
119    #[inline]
120    pub fn frac_units(&self) -> usize {
121        get_units(self.frac())
122    }
123
124    #[inline]
125    fn reset_units(&mut self) {
126        self.lsu.iter_mut().for_each(|n| *n = 0);
127    }
128
129    #[inline]
130    pub fn as_i64(&self) -> Result<i64> {
131        if self < &MIN_I64 || self > &MAX_I64 {
132            return Err(Error::ExceedsConversionTargetRange)
133        }
134        if self.frac_units() > 0 {
135            let mut target = Self::zero();
136            self.round_to(&mut target, 0);
137            return Ok(target.lsu_to_i64())
138        }
139        Ok(self.lsu_to_i64())
140    }
141
142    fn lsu_to_i64(&self) -> i64 {
143        if self.is_neg() {
144            -self.lsu[0] as i64 - self.lsu[1] as i64 * UNIT as i64 - self.lsu[2] as i64 * UNIT as i64 * UNIT as i64
145        } else {
146            self.lsu[0] as i64 + self.lsu[1] as i64 * UNIT as i64 + self.lsu[2] as i64 * UNIT as i64 * UNIT as i64
147        }
148    }
149
150    #[inline]
151    pub fn as_u64(&self) -> Result<u64> {
152        if self.is_neg() || self > &MAX_U64 {
153            return Err(Error::ExceedsConversionTargetRange)
154        }
155        if self.frac_units() > 0 {
156            let mut target = Self::zero();
157            self.round_to(&mut target, 0);
158            return Ok(target.lsu_to_u64())
159        }
160        Ok(self.lsu_to_u64())
161    }
162
163    fn lsu_to_u64(&self) -> u64 {
164        self.lsu[0] as u64 + self.lsu[1] as u64 * UNIT as u64 + self.lsu[2] as u64 * UNIT as u64 * UNIT as u64
165    }
166}
167
168/// Simple hash implementation on FixedDecimal.
169///
170/// NOTE: Same numeric values with different number of
171/// internal units will have different hash code.
172impl Hash for FixedDecimal {
173    fn hash<H: Hasher>(&self, state: &mut H) {
174        // hash neg flag
175        state.write_u8(if self.is_neg() { 1 } else { 0 });
176        // hash nbr of intg units
177        let intg_units = self.intg_units();
178        state.write_u8(intg_units as u8);
179        // hash nbr of frac units
180        let frac_units = self.frac_units();
181        state.write_u8(frac_units as u8);
182        // hash all units
183        for v in &self.lsu[..intg_units + frac_units] {
184            state.write_u32(*v as u32)
185        }
186    }
187}
188
189impl From<i64> for FixedDecimal {
190    fn from(src: i64) -> Self {
191        if src == i64::MIN {
192            return MIN_I64.clone();
193        }
194        if src < 0 {
195            let mut fd = FixedDecimal::from(-src as u64);
196            fd.set_neg();
197            fd
198        } else {
199            FixedDecimal::from(src as u64)
200        }
201    }
202}
203
204impl From<u64> for FixedDecimal {
205    fn from(mut src: u64) -> Self {
206        let mut fd = FixedDecimal::zero();
207        let mut i = 0;
208        while src != 0 {
209            let q = src / UNIT as u64;
210            let r = src - q * UNIT as u64;
211            fd.lsu[i] = r as i32;
212            i += 1;
213            src = q;
214        }
215        fd.intg = (i * DIGITS_PER_UNIT) as i8;
216        fd
217    }
218}
219
220#[cfg(test)]
221mod tests {
222
223    use std::str::FromStr;
224
225    use super::*;
226
227    #[test]
228    fn test_one() {
229        let fd = FixedDecimal::one();
230        assert!(!fd.is_neg());
231        assert!(!fd.is_zero());
232    }
233
234    #[test]
235    fn test_from_str_rand() {
236        use rand::prelude::*;
237        let mut rng = rand::thread_rng();
238        let mut fd = FixedDecimal::zero();
239        for _ in 0..128 {
240            let intg: u32 = rng.gen_range(0..1 << 30);
241            let frac: u32 = rng.gen_range(0..1 << 20);
242            let s = if frac > 0 {
243                format!("{}.{}", intg, frac)
244            } else {
245                format!("{}", intg)
246            };
247            assert!(fd.from_ascii_str(&s, true).is_ok());
248            println!("s={}, fd={:?}", s, fd);
249        }
250    }
251
252    #[test]
253    fn test_hash() {
254        for (s1, s2) in vec![("0.1", "0.10"), ("1.2", "1.2")] {
255            let fd1: FixedDecimal = s1.parse().unwrap();
256            let fd2: FixedDecimal = s2.parse().unwrap();
257            assert_eq!(decimal_hash(&fd1), decimal_hash(&fd2));
258        }
259    }
260
261    #[test]
262    fn test_from_i64() {
263        for (i, s) in vec![
264            (0i64, "0"),
265            (1, "1"),
266            (-1, "-1"),
267            (100, "100"),
268            (-100, "-100"),
269            (12345123456789, "12345123456789"),
270            (-12345123456789, "-12345123456789"),
271            (-9223372036854775808, "-9223372036854775808"),
272            (9223372036854775807, "9223372036854775807"),
273        ] {
274            let fd1 = FixedDecimal::from(i);
275            let fd2 = FixedDecimal::from_str(s).unwrap();
276            assert_eq!(fd1, fd2);
277            println!("{:?}", fd1);
278        }
279    }
280
281    #[test]
282    fn test_to_i64() {
283        // success
284        for (s, i) in vec![
285            ("0", 0i64),
286            ("1", 1),
287            ("1.1", 1),
288            ("1.5", 2),
289            ("3.8", 4),
290            ("-1.2", -1),
291            ("-5.9", -6),
292            ("100", 100),
293            ("12345123456789", 12345123456789),
294            ("-9223372036854775808", -9223372036854775808),
295            ("9223372036854775807", 9223372036854775807),
296        ] {
297            let fd = FixedDecimal::from_str(s).unwrap();
298            let res = fd.as_i64().unwrap();
299            assert_eq!(res, i)
300        }
301        // fail
302        for s in vec![
303            "-9223372036854775809",
304            "9223372036854775808",
305            "10000000000000000000000",
306        ] {
307            let fd = FixedDecimal::from_str(s).unwrap();
308            assert!(fd.as_i64().is_err());
309        }
310    }
311
312    #[test]
313    fn test_from_u64() {
314        for (i, s) in vec![
315            (0u64, "0"),
316            (1, "1"),
317            (100, "100"),
318            (12345123456789, "12345123456789"),
319            (18446744073709551615, "18446744073709551615"),
320        ] {
321            let fd1 = FixedDecimal::from(i);
322            let fd2 = FixedDecimal::from_str(s).unwrap();
323            assert_eq!(fd1, fd2);
324            println!("{:?}", fd1);
325        }
326    }
327
328    #[test]
329    fn test_to_u64() {
330        // success
331        for (s, i) in vec![
332            ("0", 0u64),
333            ("1", 1),
334            ("1.1", 1),
335            ("1.5", 2),
336            ("3.8", 4),
337            ("100", 100),
338            ("12345123456789", 12345123456789),
339            ("9223372036854775807", 9223372036854775807),
340            ("18446744073709551615", 18446744073709551615),
341        ] {
342            let fd = FixedDecimal::from_str(s).unwrap();
343            let res = fd.as_u64().unwrap();
344            assert_eq!(res, i)
345        }
346        // fail
347        for s in vec![
348            "-1",
349            "18446744073709551616",
350            "10000000000000000000000",
351        ] {
352            let fd = FixedDecimal::from_str(s).unwrap();
353            assert!(fd.as_u64().is_err());
354        }
355    }
356
357    #[inline]
358    fn decimal_hash(fd: &FixedDecimal) -> u64 {
359        use std::collections::hash_map::DefaultHasher;
360        let mut hasher = DefaultHasher::new();
361        fd.hash(&mut hasher);
362        hasher.finish()
363    }
364}