1use std::cmp::Ordering;
17
18use rust_decimal::Decimal;
19use rust_decimal::prelude::ToPrimitive;
20
21#[derive(Debug, Clone, Copy, PartialEq)]
23pub enum Numeric {
24 Int(i128),
25 Decimal(Decimal),
26 Float(f64),
27}
28
29pub fn cmp_f64(a: f64, b: f64) -> Ordering {
32 match a.partial_cmp(&b) {
33 Some(order) => order,
34 None => a.is_nan().cmp(&b.is_nan()),
35 }
36}
37
38pub fn parse_numeric_str(s: &str) -> Option<Numeric> {
43 if let Ok(i) = s.parse::<i128>() {
44 return Some(Numeric::Int(i));
45 }
46 let float = s.parse::<f64>().ok()?;
47 Some(match Decimal::from_str_exact(s) {
48 Ok(d) => decimal_reading(&d),
49 Err(_) => Numeric::Float(float),
50 })
51}
52
53pub fn decimal_reading(d: &Decimal) -> Numeric {
56 if d.is_integer()
57 && let Some(i) = d.to_i128()
58 {
59 return Numeric::Int(i);
60 }
61 Numeric::Decimal(*d)
62}
63
64const TWO_POW_127: f64 = 170_141_183_460_469_231_731_687_303_715_884_105_728.0;
66
67fn cmp_int_float(i: i128, f: f64) -> Ordering {
70 if f.is_nan() || f >= TWO_POW_127 {
71 return Ordering::Less;
72 }
73 if f < -TWO_POW_127 {
74 return Ordering::Greater;
75 }
76 let whole = f.trunc();
78 let by_whole = i.cmp(&(whole as i128));
79 if by_whole != Ordering::Equal {
80 return by_whole;
81 }
82 let fraction = f - whole;
83 if fraction > 0.0 {
84 Ordering::Less
85 } else if fraction < 0.0 {
86 Ordering::Greater
87 } else {
88 Ordering::Equal
89 }
90}
91
92fn cmp_int_decimal(i: i128, d: Decimal) -> Ordering {
95 match Decimal::try_from_i128_with_scale(i, 0) {
96 Ok(as_decimal) => as_decimal.cmp(&d),
97 Err(_) if i > 0 => Ordering::Greater,
98 Err(_) => Ordering::Less,
99 }
100}
101
102pub fn cmp_numeric(a: Numeric, b: Numeric) -> Ordering {
104 match (a, b) {
105 (Numeric::Int(x), Numeric::Int(y)) => x.cmp(&y),
106 (Numeric::Int(x), Numeric::Decimal(y)) => cmp_int_decimal(x, y),
107 (Numeric::Decimal(x), Numeric::Int(y)) => cmp_int_decimal(y, x).reverse(),
108 (Numeric::Decimal(x), Numeric::Decimal(y)) => x.cmp(&y),
109 (Numeric::Int(x), Numeric::Float(y)) => cmp_int_float(x, y),
110 (Numeric::Float(x), Numeric::Int(y)) => cmp_int_float(y, x).reverse(),
111 (Numeric::Decimal(x), Numeric::Float(y)) => cmp_f64(x.as_f64(), y),
112 (Numeric::Float(x), Numeric::Decimal(y)) => cmp_f64(x, y.as_f64()),
113 (Numeric::Float(x), Numeric::Float(y)) => cmp_f64(x, y),
114 }
115}
116
117pub fn numeric_bucket_bits(n: Numeric) -> u64 {
123 let f = match n {
124 Numeric::Int(i) => i as f64,
125 Numeric::Decimal(d) => d.as_f64(),
126 Numeric::Float(f) => f,
127 };
128 if f.is_nan() {
129 f64::NAN.to_bits()
130 } else if f == 0.0 {
131 0.0f64.to_bits()
132 } else {
133 f.to_bits()
134 }
135}
136
137pub fn numeric_eq(a: Numeric, b: Numeric) -> bool {
141 cmp_numeric(a, b) == Ordering::Equal
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 fn dec(text: &str) -> Decimal {
149 Decimal::from_str_exact(text).unwrap()
150 }
151
152 #[test]
153 fn nan_is_above_every_number_and_equals_nan() {
154 assert_eq!(cmp_f64(f64::NAN, f64::INFINITY), Ordering::Greater);
155 assert_eq!(cmp_f64(f64::NEG_INFINITY, f64::NAN), Ordering::Less);
156 assert_eq!(cmp_f64(f64::NAN, f64::NAN), Ordering::Equal);
157 assert_eq!(cmp_f64(-0.0, 0.0), Ordering::Equal);
158 let nan = Numeric::Float(f64::NAN);
159 assert_eq!(cmp_numeric(nan, Numeric::Int(i128::MAX)), Ordering::Greater);
160 assert_eq!(cmp_numeric(Numeric::Int(i128::MIN), nan), Ordering::Less);
161 assert_eq!(
162 cmp_numeric(nan, Numeric::Decimal(Decimal::MAX)),
163 Ordering::Greater
164 );
165 assert_eq!(
166 cmp_numeric(Numeric::Decimal(dec("0.5")), nan),
167 Ordering::Less
168 );
169 assert_eq!(cmp_numeric(nan, nan), Ordering::Equal);
170 assert!(numeric_eq(nan, nan));
171 }
172
173 #[test]
176 fn mixed_sort_is_total() {
177 let mut values = [
178 Numeric::Float(f64::NAN),
179 Numeric::Int(3),
180 Numeric::Float(f64::INFINITY),
181 Numeric::Decimal(dec("2.5")),
182 Numeric::Float(f64::NAN),
183 Numeric::Float(f64::NEG_INFINITY),
184 Numeric::Int(-1),
185 ];
186 values.sort_by(|a, b| cmp_numeric(*a, *b));
187 assert_eq!(values[0], Numeric::Float(f64::NEG_INFINITY));
188 assert_eq!(values[1], Numeric::Int(-1));
189 assert_eq!(values[2], Numeric::Decimal(dec("2.5")));
190 assert_eq!(values[3], Numeric::Int(3));
191 assert_eq!(values[4], Numeric::Float(f64::INFINITY));
192 assert!(matches!(values[5], Numeric::Float(f) if f.is_nan()));
193 assert!(matches!(values[6], Numeric::Float(f) if f.is_nan()));
194 }
195
196 #[test]
198 fn fractional_decimal_text_compares_exactly() {
199 let low = parse_numeric_str("12345678901234567.01").unwrap();
200 let high = parse_numeric_str("12345678901234567.02").unwrap();
201 assert_eq!(low, Numeric::Decimal(dec("12345678901234567.01")));
202 assert_eq!(cmp_numeric(low, high), Ordering::Less);
203 assert_eq!(cmp_numeric(high, low), Ordering::Greater);
204 assert!(!numeric_eq(low, high));
205 }
206
207 #[test]
208 fn decimal_against_integer_compares_exactly() {
209 let above = Numeric::Decimal(dec("9007199254740993.5"));
210 assert_eq!(
211 cmp_numeric(above, Numeric::Int(9_007_199_254_740_993)),
212 Ordering::Greater
213 );
214 assert_eq!(
215 cmp_numeric(Numeric::Int(9_007_199_254_740_994), above),
216 Ordering::Greater
217 );
218 assert_eq!(
219 cmp_numeric(Numeric::Int(i128::MAX), above),
220 Ordering::Greater
221 );
222 assert_eq!(cmp_numeric(Numeric::Int(i128::MIN), above), Ordering::Less);
223 assert_eq!(
224 cmp_numeric(Numeric::Decimal(dec("-0.5")), Numeric::Int(0)),
225 Ordering::Less
226 );
227 }
228
229 #[test]
230 fn numeric_text_reads_by_kind() {
231 assert_eq!(parse_numeric_str("12"), Some(Numeric::Int(12)));
232 assert_eq!(parse_numeric_str("5.0"), Some(Numeric::Int(5)));
233 assert_eq!(parse_numeric_str("0.1"), Some(Numeric::Decimal(dec("0.1"))));
234 assert_eq!(parse_numeric_str("1e3"), Some(Numeric::Float(1000.0)));
235 assert!(matches!(parse_numeric_str("NaN"), Some(Numeric::Float(f)) if f.is_nan()));
236 assert_eq!(parse_numeric_str("x"), None);
237 assert_eq!(parse_numeric_str("1_0.5"), None);
238 }
239
240 #[test]
241 fn integral_decimal_reads_as_integer() {
242 assert_eq!(decimal_reading(&dec("42.000")), Numeric::Int(42));
243 assert_eq!(
244 decimal_reading(&Decimal::MAX),
245 Numeric::Int(Decimal::MAX.mantissa())
246 );
247 assert_eq!(decimal_reading(&dec("0.25")), Numeric::Decimal(dec("0.25")));
248 }
249}