Skip to main content

icydb_schema/decimal/
compare.rs

1use crate::decimal::{DECIMAL_DIGIT_BUFFER_LEN, Decimal};
2use std::{
3    cmp::Ordering,
4    hash::{Hash, Hasher},
5};
6
7impl Decimal {
8    pub(in crate::decimal) fn cmp_decimal(&self, other: &Self) -> Ordering {
9        let (lhs_m, lhs_s) = self.normalized_parts();
10        let (rhs_m, rhs_s) = other.normalized_parts();
11
12        if lhs_m == rhs_m && lhs_s == rhs_s {
13            return Ordering::Equal;
14        }
15
16        if lhs_m == 0 {
17            return if rhs_m.is_negative() {
18                Ordering::Greater
19            } else {
20                Ordering::Less
21            };
22        }
23
24        if rhs_m == 0 {
25            return if lhs_m.is_negative() {
26                Ordering::Less
27            } else {
28                Ordering::Greater
29            };
30        }
31
32        if lhs_m.is_negative() != rhs_m.is_negative() {
33            return if lhs_m.is_negative() {
34                Ordering::Less
35            } else {
36                Ordering::Greater
37            };
38        }
39
40        let positive = !lhs_m.is_negative();
41        let mut lhs_digits = [0u8; DECIMAL_DIGIT_BUFFER_LEN];
42        let mut rhs_digits = [0u8; DECIMAL_DIGIT_BUFFER_LEN];
43        let lhs_len = write_u128_decimal_digits(lhs_m.unsigned_abs(), &mut lhs_digits);
44        let rhs_len = write_u128_decimal_digits(rhs_m.unsigned_abs(), &mut rhs_digits);
45
46        let lhs_exponent = compare_exponent(lhs_s, lhs_len).unwrap_or(i64::MIN);
47        let rhs_exponent = compare_exponent(rhs_s, rhs_len).unwrap_or(i64::MIN);
48
49        let exponent_cmp = lhs_exponent.cmp(&rhs_exponent);
50        if exponent_cmp != Ordering::Equal {
51            return if positive {
52                exponent_cmp
53            } else {
54                exponent_cmp.reverse()
55            };
56        }
57
58        let significand_cmp =
59            cmp_significand_digits(&lhs_digits[..lhs_len], &rhs_digits[..rhs_len]);
60        if positive {
61            significand_cmp
62        } else {
63            significand_cmp.reverse()
64        }
65    }
66}
67
68impl PartialEq for Decimal {
69    fn eq(&self, other: &Self) -> bool {
70        let (lhs_m, lhs_s) = self.normalized_parts();
71        let (rhs_m, rhs_s) = other.normalized_parts();
72        lhs_m == rhs_m && lhs_s == rhs_s
73    }
74}
75
76impl Eq for Decimal {}
77
78impl PartialOrd for Decimal {
79    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
80        Some(Ord::cmp(self, other))
81    }
82}
83
84impl Ord for Decimal {
85    fn cmp(&self, other: &Self) -> Ordering {
86        self.cmp_decimal(other)
87    }
88}
89
90impl Hash for Decimal {
91    fn hash<H: Hasher>(&self, state: &mut H) {
92        let (mantissa, scale) = self.normalized_parts();
93        mantissa.hash(state);
94        scale.hash(state);
95    }
96}
97
98fn write_u128_decimal_digits(mut value: u128, out: &mut [u8; DECIMAL_DIGIT_BUFFER_LEN]) -> usize {
99    let mut write_idx = DECIMAL_DIGIT_BUFFER_LEN;
100
101    loop {
102        write_idx = write_idx.saturating_sub(1);
103        out[write_idx] = match value % 10 {
104            0 => b'0',
105            1 => b'1',
106            2 => b'2',
107            3 => b'3',
108            4 => b'4',
109            5 => b'5',
110            6 => b'6',
111            7 => b'7',
112            8 => b'8',
113            9 => b'9',
114            _ => unreachable!("decimal digit remainder must be in 0..=9"),
115        };
116        value /= 10;
117
118        if value == 0 {
119            break;
120        }
121    }
122
123    let len = DECIMAL_DIGIT_BUFFER_LEN.saturating_sub(write_idx);
124    out.copy_within(write_idx..DECIMAL_DIGIT_BUFFER_LEN, 0);
125    len
126}
127
128fn compare_exponent(scale: u32, digit_len: usize) -> Option<i64> {
129    let digit_count = i64::try_from(digit_len).ok()?;
130    let scale = i64::from(scale);
131    digit_count.checked_sub(1)?.checked_sub(scale)
132}
133
134fn cmp_significand_digits(lhs: &[u8], rhs: &[u8]) -> Ordering {
135    let width = lhs.len().max(rhs.len());
136    for idx in 0..width {
137        let l = lhs.get(idx).copied().unwrap_or(b'0');
138        let r = rhs.get(idx).copied().unwrap_or(b'0');
139        let cmp = l.cmp(&r);
140        if cmp != Ordering::Equal {
141            return cmp;
142        }
143    }
144
145    Ordering::Equal
146}