use std::cmp::Ordering;
pub(crate) fn compare_values(a: &str, b: &str) -> Ordering {
compare_decimal_strings(a, b).unwrap_or_else(|| a.cmp(b))
}
fn is_decimal_string(s: &str) -> bool {
let s = s.trim();
if s.is_empty() {
return false;
}
let s = s
.strip_prefix('-')
.or_else(|| s.strip_prefix('+'))
.unwrap_or(s);
if s.is_empty() {
return false;
}
let mut has_digit = false;
let mut has_dot = false;
for c in s.chars() {
if c.is_ascii_digit() {
has_digit = true;
} else if c == '.' && !has_dot {
has_dot = true;
} else {
return false;
}
}
has_digit
}
fn compare_decimal_strings(a: &str, b: &str) -> Option<Ordering> {
let a = a.trim();
let b = b.trim();
if !is_decimal_string(a) || !is_decimal_string(b) {
return None;
}
let (a_neg, a_abs) = if let Some(rest) = a.strip_prefix('-') {
(true, rest)
} else if let Some(rest) = a.strip_prefix('+') {
(false, rest)
} else {
(false, a)
};
let (b_neg, b_abs) = if let Some(rest) = b.strip_prefix('-') {
(true, rest)
} else if let Some(rest) = b.strip_prefix('+') {
(false, rest)
} else {
(false, b)
};
let (a_int, a_frac) = split_decimal(a_abs);
let (b_int, b_frac) = split_decimal(b_abs);
let a_is_zero = is_zero(a_int, a_frac);
let b_is_zero = is_zero(b_int, b_frac);
if a_is_zero && b_is_zero {
return Some(Ordering::Equal);
}
if a_neg && !a_is_zero && (!b_neg || b_is_zero) {
return Some(Ordering::Less);
}
if (!a_neg || a_is_zero) && b_neg && !b_is_zero {
return Some(Ordering::Greater);
}
let abs_cmp = compare_abs(a_int, a_frac, b_int, b_frac)?;
if a_neg && b_neg {
Some(abs_cmp.reverse())
} else {
Some(abs_cmp)
}
}
fn split_decimal(s: &str) -> (&str, &str) {
if let Some(dot) = s.find('.') {
(&s[..dot], &s[dot + 1..])
} else {
(s, "")
}
}
fn is_zero(int_part: &str, frac_part: &str) -> bool {
int_part.chars().all(|c| c == '0') && frac_part.chars().all(|c| c == '0')
}
fn compare_abs(a_int: &str, a_frac: &str, b_int: &str, b_frac: &str) -> Option<Ordering> {
let a_int = a_int.trim_start_matches('0');
let b_int = b_int.trim_start_matches('0');
match a_int.len().cmp(&b_int.len()) {
Ordering::Less => return Some(Ordering::Less),
Ordering::Greater => return Some(Ordering::Greater),
Ordering::Equal => match a_int.cmp(b_int) {
Ordering::Less => return Some(Ordering::Less),
Ordering::Greater => return Some(Ordering::Greater),
Ordering::Equal => {}
},
}
let max_frac = a_frac.len().max(b_frac.len());
for i in 0..max_frac {
let a_digit = a_frac.as_bytes().get(i).copied().unwrap_or(b'0');
let b_digit = b_frac.as_bytes().get(i).copied().unwrap_or(b'0');
match a_digit.cmp(&b_digit) {
Ordering::Less => return Some(Ordering::Less),
Ordering::Greater => return Some(Ordering::Greater),
Ordering::Equal => {}
}
}
Some(Ordering::Equal)
}