1use std::cmp::Ordering;
19use std::fmt;
20
21#[derive(Clone, Debug)]
25pub enum Number {
26 Float(f64),
27 Integer(i64),
28 BigInteger(String),
29}
30
31impl PartialEq for Number {
33 fn eq(&self, other: &Self) -> bool {
34 match (self, other) {
35 (Number::Float(v1), Number::Float(v2)) => (v1 - v2).abs() < f64::EPSILON,
36 (Number::Integer(v1), Number::Integer(v2)) => v1 == v2,
37 (Number::BigInteger(v1), Number::BigInteger(v2)) => v1 == v2,
38 _ => false,
39 }
40 }
41}
42
43impl Eq for Number {}
44
45impl fmt::Display for Number {
46 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
47 let value = match self {
48 Number::Float(f) => format_float(*f),
49 Number::Integer(x) => x.to_string(),
50 Number::BigInteger(s) => s.to_string(),
51 };
52 write!(f, "{value}")
53 }
54}
55
56fn format_float(value: f64) -> String {
57 if value.fract() < f64::EPSILON {
58 format!("{value}.0")
59 } else {
60 value.to_string()
61 }
62}
63
64impl From<f64> for Number {
65 fn from(value: f64) -> Self {
66 Number::Float(value)
67 }
68}
69
70impl From<i64> for Number {
71 fn from(value: i64) -> Self {
72 Number::Integer(value)
73 }
74}
75
76impl Number {
77 pub fn cmp_value(&self, other: &Number) -> Ordering {
78 match (self, other) {
79 (Number::Integer(i1), Number::Integer(i2)) => i1.cmp(i2),
80 (Number::Float(f1), Number::Float(f2)) => compare_float(*f1, *f2),
81 (Number::Integer(i1), Number::Float(f2)) => compare_float(*i1 as f64, *f2),
82 (Number::Float(f1), Number::Integer(i2)) => compare_float(*f1, *i2 as f64),
83 (n1, n2) => compare_number_string(&n1.to_string(), &n2.to_string()),
84 }
85 }
86}
87
88fn compare_float(f1: f64, f2: f64) -> Ordering {
89 if f1 > f2 {
90 Ordering::Greater
91 } else if f1 < f2 {
92 Ordering::Less
93 } else {
94 Ordering::Equal
95 }
96}
97
98fn compare_number_string(n1: &str, n2: &str) -> Ordering {
99 let (neg1, i1, d1) = number_components(n1);
100 let (neg2, i2, d2) = number_components(n2);
101 if neg1 == neg2 {
102 match i1.cmp(i2) {
103 Ordering::Less => Ordering::Less,
104 Ordering::Greater => Ordering::Greater,
105 Ordering::Equal => d1.cmp(d2),
106 }
107 } else if neg1 {
108 Ordering::Less
109 } else {
110 Ordering::Greater
111 }
112}
113
114fn number_components(s: &str) -> (bool, &str, &str) {
116 match s.strip_prefix('-') {
117 None => match s.find('.') {
118 None => (false, s.trim_start_matches('0'), ""),
119 Some(index) => (
120 false,
121 (s[..index].trim_start_matches('0')),
122 (s[(index + 1)..].trim_end_matches('0')),
123 ),
124 },
125 Some(s) => {
126 let (_, integer, decimal) = number_components(s);
127 (true, integer, decimal)
128 }
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
137 fn test_to_string() {
138 assert_eq!(Number::Float(1.0).to_string(), "1.0".to_string());
139 assert_eq!(Number::Float(1.1).to_string(), "1.1".to_string());
140 assert_eq!(Number::from(1.0).to_string(), "1.0".to_string());
141 assert_eq!(Number::from(1.1).to_string(), "1.1".to_string());
142 assert_eq!(
143 Number::BigInteger("1.1".to_string()).to_string(),
144 "1.1".to_string()
145 );
146 assert_eq!(
147 Number::BigInteger("1".to_string()).to_string(),
148 "1".to_string()
149 );
150 }
151
152 #[test]
153 fn test_cmp_value() {
154 let integer_zero = Number::from(0);
155 let integer_minus_one = Number::from(-1);
156 let integer_one = Number::from(1);
157 let integer_two = Number::from(2);
158 let integer_max = Number::from(i64::MAX);
159 let integer_min = Number::from(i64::MIN);
160
161 let float_zero = Number::from(0.0);
162 let float_one = Number::from(1.0);
163 let float_one_plus_epsilon = Number::from(1.000_000_000_000_000_100);
164 let float_one_plus_plus_epsilon = Number::from(1.000_000_000_000_001);
165 let float_two = Number::from(2.0);
166 let float_min = Number::from(f64::MIN);
167 let float_max = Number::from(f64::MAX);
168
169 let number_one = Number::BigInteger("1".to_string());
170 let number_two = Number::BigInteger("2".to_string());
171 let number_two_with_decimal = Number::BigInteger("2.0".to_string());
172
173 assert_eq!(integer_minus_one.cmp_value(&integer_zero), Ordering::Less);
174
175 assert_eq!(integer_one.cmp_value(&integer_one), Ordering::Equal);
176 assert_eq!(integer_one.cmp_value(&number_one), Ordering::Equal);
177 assert_eq!(integer_one.cmp_value(&float_one), Ordering::Equal);
178 assert_eq!(integer_one.cmp_value(&integer_zero), Ordering::Greater);
179 assert_eq!(integer_one.cmp_value(&float_zero), Ordering::Greater);
180 assert_eq!(integer_one.cmp_value(&integer_two), Ordering::Less);
181 assert_eq!(integer_one.cmp_value(&float_two), Ordering::Less);
182 assert_eq!(integer_one.cmp_value(&number_two), Ordering::Less);
183 assert_eq!(
184 integer_one.cmp_value(&number_two_with_decimal),
185 Ordering::Less
186 );
187
188 assert_eq!(integer_min.cmp_value(&float_min), Ordering::Greater);
189 assert_eq!(integer_max.cmp_value(&float_max), Ordering::Less);
190
191 assert_eq!(float_one.cmp_value(&float_one), Ordering::Equal);
192 assert_eq!(
193 float_one.cmp_value(&float_one_plus_epsilon),
194 Ordering::Equal
195 );
196 assert_eq!(
197 float_one.cmp_value(&float_one_plus_plus_epsilon),
198 Ordering::Less
199 );
200
201 assert_eq!(
205 Number::from(9_007_199_254_740_992.0).cmp_value(&Number::from(9_007_199_254_740_993)),
206 Ordering::Equal
207 );
208 }
209
210 #[test]
211 fn test_cmp_number_string() {
212 assert_eq!(compare_number_string("1", "1"), Ordering::Equal);
213 assert_eq!(compare_number_string("1", "1.0"), Ordering::Equal);
214 assert_eq!(compare_number_string("1.000", "1.0"), Ordering::Equal);
215 assert_eq!(compare_number_string("1", "2"), Ordering::Less);
216 assert_eq!(compare_number_string("1.1", "2"), Ordering::Less);
217 assert_eq!(compare_number_string("-001.1000", "-1.1"), Ordering::Equal);
218 }
219
220 #[test]
221 fn test_number_components() {
222 assert_eq!(number_components("1"), (false, "1", ""));
223 assert_eq!(number_components("1.0"), (false, "1", ""));
224 assert_eq!(number_components("01"), (false, "1", ""));
225
226 assert_eq!(number_components("1.1"), (false, "1", "1"));
227 assert_eq!(number_components("1.100"), (false, "1", "1"));
228
229 assert_eq!(number_components("-1.1"), (true, "1", "1"));
230 assert_eq!(number_components("-01.100"), (true, "1", "1"));
231 }
232}