gel_protocol/model/
bignum.rs

1use std::fmt::Write;
2
3#[cfg(feature = "num-bigint")]
4mod num_bigint_interop;
5
6#[cfg(feature = "bigdecimal")]
7mod bigdecimal_interop;
8
9/// Virtually unlimited precision integer.
10///
11/// See Gel [protocol documentation](https://docs.edgedb.com/database/reference/protocol/dataformats#std-bigint).
12#[derive(Clone, Debug, PartialEq)]
13#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
14pub struct BigInt {
15    pub(crate) negative: bool,
16    pub(crate) weight: i16,
17    pub(crate) digits: Vec<u16>,
18}
19
20/// High-precision decimal number.
21///
22/// See Gel [protocol documentation](https://docs.edgedb.com/database/reference/protocol/dataformats#std-decimal).
23#[derive(Clone, Debug, PartialEq)]
24#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
25pub struct Decimal {
26    pub(crate) negative: bool,
27    pub(crate) weight: i16,
28    pub(crate) decimal_digits: u16,
29    pub(crate) digits: Vec<u16>,
30}
31
32impl BigInt {
33    pub fn negative(&self) -> bool {
34        self.negative
35    }
36
37    pub fn weight(&self) -> i16 {
38        self.weight
39    }
40
41    pub fn digits(&self) -> &[u16] {
42        &self.digits
43    }
44
45    fn normalize(mut self) -> BigInt {
46        while let Some(0) = self.digits.last() {
47            self.digits.pop();
48        }
49        while let Some(0) = self.digits.first() {
50            self.digits.remove(0);
51            self.weight -= 1;
52        }
53        self
54    }
55
56    fn trailing_zero_groups(&self) -> i16 {
57        self.weight - self.digits.len() as i16 + 1
58    }
59}
60
61impl std::fmt::Display for BigInt {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        if self.negative {
64            write!(f, "-")?;
65        }
66        if let Some(digit) = self.digits.first() {
67            write!(f, "{digit}")?;
68            for digit in &mut self.digits.iter().skip(1) {
69                write!(f, "{digit:04}")?;
70            }
71            let trailing_zero_groups = self.trailing_zero_groups();
72            debug_assert!(trailing_zero_groups >= 0);
73            for _ in 0..trailing_zero_groups {
74                write!(f, "0000")?;
75            }
76        } else {
77            write!(f, "0")?;
78        }
79        Ok(())
80    }
81}
82
83impl From<u64> for BigInt {
84    fn from(v: u64) -> BigInt {
85        BigInt {
86            negative: false,
87            weight: 4,
88            digits: vec![
89                (v / 10_000_000_000_000_000 % 10000) as u16,
90                (v / 1_000_000_000_000 % 10000) as u16,
91                (v / 100_000_000 % 10000) as u16,
92                (v / 10000 % 10000) as u16,
93                (v % 10000) as u16,
94            ],
95        }
96        .normalize()
97    }
98}
99
100impl From<i64> for BigInt {
101    fn from(v: i64) -> BigInt {
102        let (abs, negative) = if v < 0 {
103            (u64::MAX - v as u64 + 1, true)
104        } else {
105            (v as u64, false)
106        };
107        BigInt {
108            negative,
109            weight: 4,
110            digits: vec![
111                (abs / 10_000_000_000_000_000 % 10000) as u16,
112                (abs / 1_000_000_000_000 % 10000) as u16,
113                (abs / 100_000_000 % 10000) as u16,
114                (abs / 10000 % 10000) as u16,
115                (abs % 10000) as u16,
116            ],
117        }
118        .normalize()
119    }
120}
121
122impl From<u32> for BigInt {
123    fn from(v: u32) -> BigInt {
124        BigInt {
125            negative: false,
126            weight: 2,
127            digits: vec![
128                (v / 100_000_000) as u16,
129                (v / 10000 % 10000) as u16,
130                (v % 10000) as u16,
131            ],
132        }
133        .normalize()
134    }
135}
136
137impl From<i32> for BigInt {
138    fn from(v: i32) -> BigInt {
139        let (abs, negative) = if v < 0 {
140            (u32::MAX - v as u32 + 1, true)
141        } else {
142            (v as u32, false)
143        };
144        BigInt {
145            negative,
146            weight: 2,
147            digits: vec![
148                (abs / 100_000_000) as u16,
149                (abs / 10000 % 10000) as u16,
150                (abs % 10000) as u16,
151            ],
152        }
153        .normalize()
154    }
155}
156
157impl Decimal {
158    pub fn negative(&self) -> bool {
159        self.negative
160    }
161
162    pub fn weight(&self) -> i16 {
163        self.weight
164    }
165
166    pub fn decimal_digits(&self) -> u16 {
167        self.decimal_digits
168    }
169
170    pub fn digits(&self) -> &[u16] {
171        &self.digits
172    }
173
174    #[allow(dead_code)] // isn't used when BigDecimal is disabled
175    fn normalize(mut self) -> Decimal {
176        while let Some(0) = self.digits.last() {
177            self.digits.pop();
178        }
179        while let Some(0) = self.digits.first() {
180            self.digits.remove(0);
181            self.weight -= 1;
182        }
183        self
184    }
185}
186
187impl std::fmt::Display for Decimal {
188    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
189        if self.negative {
190            write!(f, "-")?;
191        }
192
193        let mut index = 0;
194
195        // integer part
196        while self.weight - index >= 0 {
197            if let Some(digit) = self.digits.get(index as usize) {
198                if index == 0 {
199                    write!(f, "{digit}")?;
200                } else {
201                    write!(f, "{digit:04}")?;
202                }
203                index += 1;
204            } else {
205                break;
206            }
207        }
208
209        // trailing zeros of the integer part
210        for _ in 0..(self.weight - index + 1) {
211            f.write_str("0000")?;
212        }
213
214        if index == 0 {
215            write!(f, "0")?;
216        }
217
218        // dot
219        write!(f, ".")?;
220
221        // leading zeros of the decimal part
222        let mut decimals = u16::max(self.decimal_digits, 1);
223        if index == 0 && self.weight < 0 {
224            for _ in 0..(-1 - self.weight) {
225                f.write_str("0000")?;
226                decimals -= 4;
227            }
228        }
229
230        while decimals > 0 {
231            if let Some(digit) = self.digits.get(index as usize) {
232                let digit = format!("{digit:04}");
233                let consumed = u16::min(4, decimals);
234                f.write_str(&digit[0..consumed as usize])?;
235                decimals -= consumed;
236                index += 1;
237            } else {
238                break;
239            }
240        }
241        // trailing zeros
242        for _ in 0..decimals {
243            f.write_char('0')?;
244        }
245        Ok(())
246    }
247}
248
249#[cfg(test)]
250#[allow(dead_code)] // used by optional tests
251mod test_helpers {
252    use rand::Rng;
253
254    pub fn gen_u64<T: Rng>(rng: &mut T) -> u64 {
255        // change distribution to generate different length more frequently
256        let max = 10_u64.pow(rng.random_range(0..20));
257        rng.random_range(0..max)
258    }
259
260    pub fn gen_i64<T: Rng>(rng: &mut T) -> i64 {
261        // change distribution to generate different length more frequently
262        let max = 10_i64.pow(rng.random_range(0..19));
263        rng.random_range(-max..max)
264    }
265
266    pub fn gen_f64<T: Rng>(rng: &mut T) -> f64 {
267        rng.random::<f64>()
268    }
269}
270
271#[cfg(test)]
272#[allow(unused_imports)] // because of optional tests
273mod test {
274    use super::{BigInt, Decimal};
275    use std::convert::TryFrom;
276    use std::str::FromStr;
277
278    #[test]
279    fn big_int_conversion() {
280        assert_eq!(BigInt::from(125u32).weight, 0);
281        assert_eq!(&BigInt::from(125u32).digits, &[125]);
282        assert_eq!(BigInt::from(30000u32).weight, 1);
283        assert_eq!(&BigInt::from(30000u32).digits, &[3]);
284        assert_eq!(BigInt::from(30001u32).weight, 1);
285        assert_eq!(&BigInt::from(30001u32).digits, &[3, 1]);
286        assert_eq!(BigInt::from(u32::MAX).weight, 2);
287        assert_eq!(BigInt::from(u32::MAX).digits, &[42, 9496, 7295]);
288
289        assert_eq!(BigInt::from(125i32).weight, 0);
290        assert_eq!(&BigInt::from(125i32).digits, &[125]);
291        assert_eq!(BigInt::from(30000i32).weight, 1);
292        assert_eq!(&BigInt::from(30000i32).digits, &[3]);
293        assert_eq!(BigInt::from(30001i32).weight, 1);
294        assert_eq!(&BigInt::from(30001i32).digits, &[3, 1]);
295        assert_eq!(BigInt::from(i32::MAX).weight, 2);
296        assert_eq!(BigInt::from(i32::MAX).digits, &[21, 4748, 3647]);
297
298        assert_eq!(BigInt::from(-125i32).weight, 0);
299        assert_eq!(&BigInt::from(-125i32).digits, &[125]);
300        assert_eq!(BigInt::from(-30000i32).weight, 1);
301        assert_eq!(&BigInt::from(-30000i32).digits, &[3]);
302        assert_eq!(BigInt::from(-30001i32).weight, 1);
303        assert_eq!(&BigInt::from(-30001i32).digits, &[3, 1]);
304        assert_eq!(BigInt::from(i32::MIN).weight, 2);
305        assert_eq!(BigInt::from(i32::MIN).digits, &[21, 4748, 3648]);
306
307        assert_eq!(BigInt::from(125u64).weight, 0);
308        assert_eq!(&BigInt::from(125u64).digits, &[125]);
309        assert_eq!(BigInt::from(30000u64).weight, 1);
310        assert_eq!(&BigInt::from(30000u64).digits, &[3]);
311        assert_eq!(BigInt::from(30001u64).weight, 1);
312        assert_eq!(&BigInt::from(30001u64).digits, &[3, 1]);
313        assert_eq!(BigInt::from(u64::MAX).weight, 4);
314        assert_eq!(BigInt::from(u64::MAX).digits, &[1844, 6744, 737, 955, 1615]);
315
316        assert_eq!(BigInt::from(125i64).weight, 0);
317        assert_eq!(&BigInt::from(125i64).digits, &[125]);
318        assert_eq!(BigInt::from(30000i64).weight, 1);
319        assert_eq!(&BigInt::from(30000i64).digits, &[3]);
320        assert_eq!(BigInt::from(30001i64).weight, 1);
321        assert_eq!(&BigInt::from(30001i64).digits, &[3, 1]);
322        assert_eq!(BigInt::from(i64::MAX).weight, 4);
323        assert_eq!(BigInt::from(i64::MAX).digits, &[922, 3372, 368, 5477, 5807]);
324
325        assert_eq!(BigInt::from(-125i64).weight, 0);
326        assert_eq!(&BigInt::from(-125i64).digits, &[125]);
327        assert_eq!(BigInt::from(-30000i64).weight, 1);
328        assert_eq!(&BigInt::from(-30000i64).digits, &[3]);
329        assert_eq!(BigInt::from(-30001i64).weight, 1);
330        assert_eq!(&BigInt::from(-30001i64).digits, &[3, 1]);
331        assert_eq!(BigInt::from(i64::MIN).weight, 4);
332        assert_eq!(BigInt::from(i64::MIN).digits, &[922, 3372, 368, 5477, 5808]);
333    }
334
335    #[test]
336    fn bigint_display() {
337        let cases = [0, 1, -1, 1_0000, -1_0000, 1_2345_6789, i64::MAX, i64::MIN];
338        for i in cases.iter() {
339            assert_eq!(BigInt::from(*i).to_string(), i.to_string());
340        }
341    }
342
343    #[test]
344    fn bigint_display_rand() {
345        use rand::{rngs::StdRng, Rng, SeedableRng};
346        let mut rng = StdRng::seed_from_u64(4);
347        for _ in 0..1000 {
348            let i = super::test_helpers::gen_i64(&mut rng);
349            assert_eq!(BigInt::from(i).to_string(), i.to_string());
350        }
351    }
352
353    #[test]
354    fn decimal_display() {
355        assert_eq!(
356            Decimal {
357                negative: false,
358                weight: 0,
359                decimal_digits: 0,
360                digits: vec![42],
361            }
362            .to_string(),
363            "42.0"
364        );
365
366        assert_eq!(
367            Decimal {
368                negative: false,
369                weight: 0,
370                decimal_digits: 10,
371                digits: vec![42],
372            }
373            .to_string(),
374            "42.0000000000"
375        );
376
377        assert_eq!(
378            Decimal {
379                negative: true,
380                weight: 0,
381                decimal_digits: 1,
382                digits: vec![42],
383            }
384            .to_string(),
385            "-42.0"
386        );
387
388        assert_eq!(
389            Decimal {
390                negative: false,
391                weight: 1,
392                decimal_digits: 10,
393                digits: vec![42],
394            }
395            .to_string(),
396            "420000.0000000000"
397        );
398
399        assert_eq!(
400            Decimal {
401                negative: false,
402                weight: -2,
403                decimal_digits: 10,
404                digits: vec![42],
405            }
406            .to_string(),
407            "0.0000004200"
408        );
409
410        assert_eq!(
411            Decimal {
412                negative: false,
413                weight: -6,
414                decimal_digits: 21,
415                digits: vec![1000],
416            }
417            .to_string(),
418            "0.000000000000000000001"
419        );
420    }
421}