use std::cmp::Ordering;
#[inline]
pub(crate) fn cassandra_double_cmp(a: f64, b: f64) -> Ordering {
match a.partial_cmp(&b) {
Some(Ordering::Equal) => sign_bit_tiebreak(a, b),
Some(ord) => ord,
None => match (a.is_nan(), b.is_nan()) {
(true, true) => Ordering::Equal,
(true, false) => Ordering::Greater,
(false, true) => Ordering::Less,
(false, false) => Ordering::Equal,
},
}
}
#[inline]
pub(crate) fn cassandra_float_cmp(a: f32, b: f32) -> Ordering {
cassandra_double_cmp(a as f64, b as f64)
}
#[inline]
fn sign_bit_tiebreak(a: f64, b: f64) -> Ordering {
match (a.is_sign_negative(), b.is_sign_negative()) {
(true, false) => Ordering::Less, (false, true) => Ordering::Greater, _ => Ordering::Equal,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nan_sorts_last_greater_than_infinity() {
assert_eq!(
cassandra_double_cmp(f64::NAN, f64::INFINITY),
Ordering::Greater
);
assert_eq!(
cassandra_double_cmp(f64::INFINITY, f64::NAN),
Ordering::Less
);
assert_eq!(cassandra_double_cmp(f64::NAN, 1.0), Ordering::Greater);
assert_eq!(cassandra_double_cmp(-1.0e300, f64::NAN), Ordering::Less);
}
#[test]
fn all_nan_bit_patterns_compare_equal() {
let quiet = f64::NAN;
let other = f64::from_bits(0xFFF8_0000_0000_0001);
assert!(other.is_nan());
assert_eq!(cassandra_double_cmp(quiet, other), Ordering::Equal);
assert_eq!(cassandra_double_cmp(other, quiet), Ordering::Equal);
assert_eq!(cassandra_double_cmp(quiet, quiet), Ordering::Equal);
}
#[test]
fn signed_zero_is_ordered() {
assert_eq!(cassandra_double_cmp(-0.0, 0.0), Ordering::Less);
assert_eq!(cassandra_double_cmp(0.0, -0.0), Ordering::Greater);
assert_eq!(cassandra_double_cmp(-0.0, -0.0), Ordering::Equal);
assert_eq!(cassandra_double_cmp(0.0, 0.0), Ordering::Equal);
}
#[test]
fn normal_numeric_order_preserved() {
assert_eq!(cassandra_double_cmp(1.0, 2.0), Ordering::Less);
assert_eq!(cassandra_double_cmp(2.0, 1.0), Ordering::Greater);
assert_eq!(cassandra_double_cmp(-1.0, 1.0), Ordering::Less);
assert_eq!(cassandra_double_cmp(5.0, 5.0), Ordering::Equal);
assert_eq!(
cassandra_double_cmp(f64::NEG_INFINITY, f64::INFINITY),
Ordering::Less
);
}
#[test]
fn full_sort_matches_cassandra_oracle_f64() {
let mut v = [
1.0,
f64::NAN,
-0.0,
0.0,
f64::NEG_INFINITY,
f64::INFINITY,
f64::NAN,
];
v.sort_by(|a, b| cassandra_double_cmp(*a, *b));
assert_eq!(v[0], f64::NEG_INFINITY);
assert!(
v[1] == 0.0 && v[1].is_sign_negative(),
"index 1 must be -0.0"
);
assert!(
v[2] == 0.0 && v[2].is_sign_positive(),
"index 2 must be +0.0"
);
assert_eq!(v[3], 1.0);
assert_eq!(v[4], f64::INFINITY);
assert!(v[5].is_nan(), "index 5 must be NaN");
assert!(v[6].is_nan(), "index 6 must be NaN");
}
#[test]
fn min_of_signed_zeros_selects_negative_zero() {
let data = [0.0_f64, -0.0_f64];
let min = data
.iter()
.copied()
.min_by(|a, b| cassandra_double_cmp(*a, *b))
.unwrap();
assert!(min == 0.0 && min.is_sign_negative(), "MIN must be -0.0");
}
#[test]
fn min_ignores_nan_max_is_nan() {
let data = [f64::NAN, 3.0, -2.0, f64::NAN, 10.0];
let min = data
.iter()
.copied()
.min_by(|a, b| cassandra_double_cmp(*a, *b))
.unwrap();
assert_eq!(min, -2.0);
let max = data
.iter()
.copied()
.max_by(|a, b| cassandra_double_cmp(*a, *b))
.unwrap();
assert!(max.is_nan(), "MAX must be NaN (sorts last)");
}
#[test]
fn full_sort_matches_cassandra_oracle_f32() {
let mut v = [
1.0_f32,
f32::NAN,
-0.0,
0.0,
f32::NEG_INFINITY,
f32::INFINITY,
f32::NAN,
];
v.sort_by(|a, b| cassandra_float_cmp(*a, *b));
assert_eq!(v[0], f32::NEG_INFINITY);
assert!(
v[1] == 0.0 && v[1].is_sign_negative(),
"index 1 must be -0.0"
);
assert!(
v[2] == 0.0 && v[2].is_sign_positive(),
"index 2 must be +0.0"
);
assert_eq!(v[3], 1.0);
assert_eq!(v[4], f32::INFINITY);
assert!(v[5].is_nan());
assert!(v[6].is_nan());
}
#[test]
fn f32_nan_last_and_signed_zero() {
assert_eq!(
cassandra_float_cmp(f32::NAN, f32::INFINITY),
Ordering::Greater
);
assert_eq!(cassandra_float_cmp(-0.0, 0.0), Ordering::Less);
assert_eq!(cassandra_float_cmp(f32::NAN, f32::NAN), Ordering::Equal);
}
}