/ src / num.rs
num.rs
  1  //! Some numerical utilities.
  2  
  3  use std::hash::Hash;
  4  
  5  /// A wrapper for `f64` that implements `Ord`.
  6  ///
  7  /// Unlike the more principled wrappers in the `ordered_float` crate, this one
  8  /// just breaks the `Ord` rules when comparing NaNs -- it doesn't order them,
  9  /// nor does it panic or guard against them on construction. This makes things
 10  /// substantially faster: I measured a 20% improvement to some benchmarks by
 11  /// switching from `OrderedFloat` to `CheapOrderedFloat`.
 12  ///
 13  /// Ignoring NaNs should be ok: we check input for NaNs and infinities, and
 14  /// then we make some effort not to introduce them.
 15  #[cfg_attr(test, derive(serde::Serialize, serde::Deserialize))]
 16  #[cfg_attr(test, serde(transparent))]
 17  #[derive(Debug, Clone, Copy, PartialEq)]
 18  pub struct CheapOrderedFloat(f64);
 19  
 20  impl std::ops::Add<CheapOrderedFloat> for CheapOrderedFloat {
 21      type Output = Self;
 22  
 23      fn add(self, rhs: CheapOrderedFloat) -> Self::Output {
 24          CheapOrderedFloat(self.0 + rhs.0)
 25      }
 26  }
 27  
 28  impl<'a> std::ops::Add<&'a CheapOrderedFloat> for CheapOrderedFloat {
 29      type Output = Self;
 30  
 31      fn add(self, rhs: &'a CheapOrderedFloat) -> Self::Output {
 32          CheapOrderedFloat(self.0 + rhs.0)
 33      }
 34  }
 35  
 36  impl std::ops::Sub<CheapOrderedFloat> for CheapOrderedFloat {
 37      type Output = Self;
 38  
 39      fn sub(self, rhs: CheapOrderedFloat) -> Self::Output {
 40          CheapOrderedFloat(self.0 - rhs.0)
 41      }
 42  }
 43  
 44  impl<'a> std::ops::Sub<&'a CheapOrderedFloat> for CheapOrderedFloat {
 45      type Output = Self;
 46  
 47      fn sub(self, rhs: &'a CheapOrderedFloat) -> Self::Output {
 48          CheapOrderedFloat(self.0 - rhs.0)
 49      }
 50  }
 51  
 52  impl std::ops::Mul<CheapOrderedFloat> for CheapOrderedFloat {
 53      type Output = Self;
 54  
 55      fn mul(self, rhs: CheapOrderedFloat) -> Self::Output {
 56          CheapOrderedFloat(self.0 * rhs.0)
 57      }
 58  }
 59  
 60  impl<'a> std::ops::Mul<&'a CheapOrderedFloat> for CheapOrderedFloat {
 61      type Output = Self;
 62  
 63      fn mul(self, rhs: &'a CheapOrderedFloat) -> Self::Output {
 64          CheapOrderedFloat(self.0 * rhs.0)
 65      }
 66  }
 67  
 68  impl std::ops::Div<CheapOrderedFloat> for CheapOrderedFloat {
 69      type Output = Self;
 70  
 71      fn div(self, rhs: CheapOrderedFloat) -> Self::Output {
 72          CheapOrderedFloat(self.0 / rhs.0)
 73      }
 74  }
 75  
 76  impl<'a> std::ops::Div<&'a CheapOrderedFloat> for CheapOrderedFloat {
 77      type Output = Self;
 78  
 79      fn div(self, rhs: &'a CheapOrderedFloat) -> Self::Output {
 80          CheapOrderedFloat(self.0 / rhs.0)
 81      }
 82  }
 83  
 84  impl std::ops::Neg for CheapOrderedFloat {
 85      type Output = Self;
 86  
 87      fn neg(self) -> Self::Output {
 88          CheapOrderedFloat(-self.0)
 89      }
 90  }
 91  
 92  impl Hash for CheapOrderedFloat {
 93      fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
 94          self.0.to_bits().hash(state)
 95      }
 96  }
 97  
 98  impl CheapOrderedFloat {
 99      /// Retrieve the inner `f64`.
100      pub fn into_inner(self) -> f64 {
101          self.0
102      }
103  }
104  
105  // Now comes the fishy stuff.
106  impl Eq for CheapOrderedFloat {}
107  
108  impl PartialOrd for CheapOrderedFloat {
109      #[inline(always)]
110      fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
111          Some(self.cmp(other))
112      }
113  }
114  
115  impl Ord for CheapOrderedFloat {
116      #[inline(always)]
117      fn cmp(&self, other: &Self) -> std::cmp::Ordering {
118          if self.0 < other.0 {
119              std::cmp::Ordering::Less
120          } else if self.0 > other.0 {
121              std::cmp::Ordering::Greater
122          } else {
123              std::cmp::Ordering::Equal
124          }
125      }
126  }
127  
128  impl From<f64> for CheapOrderedFloat {
129      fn from(value: f64) -> Self {
130          CheapOrderedFloat(value)
131      }
132  }
133  
134  #[cfg(test)]
135  pub(crate) mod tests {
136      use proptest::prelude::*;
137  
138      // Kind of like Arbitrary, but
139      // - it's a local trait, so we can impl it for whatever we want, and
140      // - it only returns "reasonable" values.
141      pub trait Reasonable {
142          type Strategy: Strategy<Value = Self>;
143          fn reasonable() -> Self::Strategy;
144      }
145  
146      impl<S: Reasonable, T: Reasonable> Reasonable for (S, T) {
147          type Strategy = (S::Strategy, T::Strategy);
148  
149          fn reasonable() -> Self::Strategy {
150              (S::reasonable(), T::reasonable())
151          }
152      }
153  
154      impl Reasonable for f64 {
155          type Strategy = BoxedStrategy<f64>;
156  
157          fn reasonable() -> Self::Strategy {
158              (-1e6..1e6).boxed()
159          }
160      }
161  }