use ordered_float::OrderedFloat;
use std::fmt;
pub(crate) type OrderedF64 = OrderedFloat<f64>;
#[inline]
pub(crate) fn canonicalize_f64(value: f64) -> OrderedF64 {
OrderedFloat(if value == 0.0 { 0.0 } else { value })
}
#[derive(Clone)]
pub struct Number {
pub(crate) n: N,
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub(crate) enum N {
NegInt(i64),
Float(OrderedF64),
}
impl PartialEq for Number {
fn eq(&self, other: &Self) -> bool {
self.n == other.n
}
}
impl Eq for Number {}
impl Ord for Number {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match (&self.n, &other.n) {
(N::NegInt(a), N::NegInt(b)) => a.cmp(b),
(N::Float(a), N::Float(b)) => a.cmp(b),
(N::NegInt(a), N::Float(b)) => canonicalize_f64(*a as f64).cmp(b),
(N::Float(a), N::NegInt(b)) => a.cmp(&canonicalize_f64(*b as f64)),
}
}
}
impl PartialOrd for Number {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl core::hash::Hash for Number {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.n.hash(state);
}
}
impl fmt::Display for N {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
N::NegInt(value) => write!(f, "{}", value),
N::Float(value) => write!(f, "{}", value),
}
}
}
impl fmt::Display for Number {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.n)
}
}
impl fmt::Debug for N {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
N::NegInt(value) => write!(f, "{}", value),
N::Float(value) => write!(f, "{}", value),
}
}
}
impl fmt::Debug for Number {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.n {
N::NegInt(value) => write!(f, "{}", value),
N::Float(value) => write!(f, "{}", value),
}
}
}
impl From<i64> for Number {
fn from(value: i64) -> Self {
Number {
n: N::NegInt(value),
}
}
}
impl From<i32> for Number {
fn from(value: i32) -> Self {
Number {
n: N::NegInt(value as i64),
}
}
}
impl From<usize> for Number {
fn from(value: usize) -> Self {
Number {
n: N::NegInt(value as i64),
}
}
}
impl From<f64> for Number {
fn from(value: f64) -> Self {
Number {
n: N::Float(canonicalize_f64(value)),
}
}
}
impl From<Number> for String {
fn from(value: Number) -> Self {
match value.n {
N::NegInt(value) => value.to_string(),
N::Float(value) => value.to_string(),
}
}
}
impl Number {
#[inline]
pub fn is_i64(&self) -> bool {
match self.n {
N::NegInt(_) => true,
N::Float(_) => false,
}
}
#[inline]
pub fn is_f64(&self) -> bool {
match self.n {
N::NegInt(_) => false,
N::Float(_) => true,
}
}
#[inline]
pub fn as_i64(&self) -> Option<i64> {
match self.n {
N::NegInt(value) => Some(value),
N::Float(_) => None,
}
}
#[inline]
pub fn as_f64(&self) -> Option<f64> {
match self.n {
N::NegInt(_) => None,
N::Float(value) => Some(value.into_inner()),
}
}
#[inline]
pub fn from_i64(value: i64) -> Number {
Number {
n: N::NegInt(value),
}
}
#[inline]
pub fn from_f64(value: f64) -> Number {
Number {
n: N::Float(canonicalize_f64(value)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use fnv::FnvHasher;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
fn calculate_hash<T: Hash>(t: &T) -> u64 {
let mut s = FnvHasher::default();
t.hash(&mut s);
s.finish()
}
#[test]
fn test_zero_and_neg_zero_equality_and_hash() {
let pos_zero = Number::from_f64(0.0);
let neg_zero = Number::from_f64(-0.0);
assert_eq!(pos_zero, neg_zero);
assert_eq!(calculate_hash(&pos_zero), calculate_hash(&neg_zero));
}
#[test]
fn test_number_hashmap_and_indexmap_lookup() {
let pos_zero = Number::from_f64(0.0);
let neg_zero = Number::from_f64(-0.0);
let mut map = HashMap::new();
map.insert(pos_zero.clone(), "zero");
assert_eq!(map.get(&neg_zero), Some(&"zero"));
let float_val = Number::from_f64(2.5);
map.insert(float_val.clone(), "two_and_half");
assert_eq!(map.get(&Number::from_f64(2.5)), Some(&"two_and_half"));
}
#[test]
fn test_nan_equality() {
let nan1 = Number::from_f64(f64::NAN);
let nan2 = Number::from_f64(f64::NAN);
assert_eq!(nan1, nan2);
assert_eq!(calculate_hash(&nan1), calculate_hash(&nan2));
}
}