From 5ec14867c88f137bbb5a25a2ed5d7f5d773fadcd Mon Sep 17 00:00:00 2001 From: Jan Haller Date: Sat, 11 Dec 2021 13:52:23 +0100 Subject: [PATCH] OrderedFloat refactor (#918) * Move egui/util/float_ord.rs -> epaint/util/ordered_float.rs * Implement Hash on OrderedFloat * Generic OrderedFloat; impl Hash; documentation --- egui/src/util/float_ord.rs | 64 -------------- egui/src/util/mod.rs | 1 - egui/src/widgets/plot/items/mod.rs | 2 +- egui/src/widgets/plot/mod.rs | 4 +- epaint/src/lib.rs | 12 +++ epaint/src/{util.rs => util/mod.rs} | 4 + epaint/src/util/ordered_float.rs | 127 ++++++++++++++++++++++++++++ 7 files changed, 146 insertions(+), 68 deletions(-) delete mode 100644 egui/src/util/float_ord.rs rename epaint/src/{util.rs => util/mod.rs} (90%) create mode 100644 epaint/src/util/ordered_float.rs diff --git a/egui/src/util/float_ord.rs b/egui/src/util/float_ord.rs deleted file mode 100644 index d4fb1b2d5d5..00000000000 --- a/egui/src/util/float_ord.rs +++ /dev/null @@ -1,64 +0,0 @@ -//! Total order on floating point types, assuming absence of NaN. -//! Can be used for sorting, min/max computation, and other collection algorithms. - -use std::cmp::Ordering; - -/// Totally orderable floating-point value -/// For not `f32` is supported; could be made generic if necessary. -pub(crate) struct OrderedFloat(f32); - -impl Eq for OrderedFloat {} - -impl PartialEq for OrderedFloat { - #[inline] - fn eq(&self, other: &Self) -> bool { - // NaNs are considered equal (equivalent when it comes to ordering - if self.0.is_nan() { - other.0.is_nan() - } else { - self.0 == other.0 - } - } -} - -impl PartialOrd for OrderedFloat { - #[inline] - fn partial_cmp(&self, other: &Self) -> Option { - match self.0.partial_cmp(&other.0) { - Some(ord) => Some(ord), - None => Some(self.0.is_nan().cmp(&other.0.is_nan())), - } - } -} - -impl Ord for OrderedFloat { - #[inline] - fn cmp(&self, other: &Self) -> Ordering { - match self.partial_cmp(other) { - Some(ord) => ord, - None => unreachable!(), - } - } -} - -/// Extension trait to provide `ord` method -pub(crate) trait FloatOrd { - /// Type to provide total order, useful as key in sorted contexts. - fn ord(self) -> OrderedFloat; -} - -impl FloatOrd for f32 { - #[inline] - fn ord(self) -> OrderedFloat { - OrderedFloat(self) - } -} - -// TODO ordering may break down at least significant digits due to f64 -> f32 conversion -// Possible solutions: generic OrderedFloat, always OrderedFloat(f64) -impl FloatOrd for f64 { - #[inline] - fn ord(self) -> OrderedFloat { - OrderedFloat(self as f32) - } -} diff --git a/egui/src/util/mod.rs b/egui/src/util/mod.rs index 0a327f0baf9..ea83ea618f6 100644 --- a/egui/src/util/mod.rs +++ b/egui/src/util/mod.rs @@ -2,7 +2,6 @@ pub mod cache; pub(crate) mod fixed_cache; -pub(crate) mod float_ord; mod history; pub mod id_type_map; pub mod undoer; diff --git a/egui/src/widgets/plot/items/mod.rs b/egui/src/widgets/plot/items/mod.rs index 9f935ca7ed8..30a2be70c29 100644 --- a/egui/src/widgets/plot/items/mod.rs +++ b/egui/src/widgets/plot/items/mod.rs @@ -2,9 +2,9 @@ use std::ops::RangeInclusive; +use epaint::util::FloatOrd; use epaint::Mesh; -use crate::util::float_ord::FloatOrd; use crate::*; use super::{PlotBounds, ScreenTransform}; diff --git a/egui/src/widgets/plot/mod.rs b/egui/src/widgets/plot/mod.rs index 768c9f18b8f..b37b0447486 100644 --- a/egui/src/widgets/plot/mod.rs +++ b/egui/src/widgets/plot/mod.rs @@ -1,9 +1,9 @@ //! Simple plotting library. -use crate::util::float_ord::FloatOrd; use crate::*; -use color::Hsva; use epaint::ahash::AHashSet; +use epaint::color::Hsva; +use epaint::util::FloatOrd; use items::PlotItem; use legend::LegendWidget; use transform::{PlotBounds, ScreenTransform}; diff --git a/epaint/src/lib.rs b/epaint/src/lib.rs index 046045e3a23..e4d75ba3cf1 100644 --- a/epaint/src/lib.rs +++ b/epaint/src/lib.rs @@ -196,3 +196,15 @@ pub(crate) fn f32_hash(state: &mut H, f: f32) { f.to_bits().hash(state); } } + +#[inline(always)] +pub(crate) fn f64_hash(state: &mut H, f: f64) { + if f == 0.0 { + state.write_u8(0); + } else if f.is_nan() { + state.write_u8(1); + } else { + use std::hash::Hash; + f.to_bits().hash(state); + } +} diff --git a/epaint/src/util.rs b/epaint/src/util/mod.rs similarity index 90% rename from epaint/src/util.rs rename to epaint/src/util/mod.rs index a321113d1d2..726dcfe3b7a 100644 --- a/epaint/src/util.rs +++ b/epaint/src/util/mod.rs @@ -1,3 +1,7 @@ +mod ordered_float; + +pub use ordered_float::*; + /// Hash the given value with a predictable hasher. #[inline] pub fn hash(value: impl std::hash::Hash) -> u64 { diff --git a/epaint/src/util/ordered_float.rs b/epaint/src/util/ordered_float.rs new file mode 100644 index 00000000000..8bbcef22dd5 --- /dev/null +++ b/epaint/src/util/ordered_float.rs @@ -0,0 +1,127 @@ +//! Total order on floating point types. +//! Can be used for sorting, min/max computation, and other collection algorithms. + +use std::cmp::Ordering; +use std::hash::{Hash, Hasher}; + +/// Wraps a floating-point value to add total order and hash. +/// Possible types for `T` are `f32` and `f64`. +/// +/// See also [`FloatOrd`]. +pub struct OrderedFloat(T); + +impl Eq for OrderedFloat {} + +impl PartialEq for OrderedFloat { + #[inline] + fn eq(&self, other: &Self) -> bool { + // NaNs are considered equal (equivalent) when it comes to ordering + if self.0.is_nan() { + other.0.is_nan() + } else { + self.0 == other.0 + } + } +} + +impl PartialOrd for OrderedFloat { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + match self.0.partial_cmp(&other.0) { + Some(ord) => Some(ord), + None => Some(self.0.is_nan().cmp(&other.0.is_nan())), + } + } +} + +impl Ord for OrderedFloat { + #[inline] + fn cmp(&self, other: &Self) -> Ordering { + match self.partial_cmp(other) { + Some(ord) => ord, + None => unreachable!(), + } + } +} + +impl Hash for OrderedFloat { + fn hash(&self, state: &mut H) { + self.0.hash(state); + } +} + +// ---------------------------------------------------------------------------- + +/// Extension trait to provide `ord()` method. +/// +/// Example with `f64`: +/// ``` +/// use epaint::util::FloatOrd; +/// +/// let array = [1.0, 2.5, 2.0]; +/// let max = array.iter().max_by_key(|val| val.ord()); +/// +/// assert_eq!(max, Some(&2.5)); +/// ``` +pub trait FloatOrd { + /// Type to provide total order, useful as key in sorted contexts. + fn ord(self) -> OrderedFloat + where + Self: Sized; +} + +impl FloatOrd for f32 { + #[inline] + fn ord(self) -> OrderedFloat { + OrderedFloat(self) + } +} + +impl FloatOrd for f64 { + #[inline] + fn ord(self) -> OrderedFloat { + OrderedFloat(self) + } +} + +// ---------------------------------------------------------------------------- + +/// Internal abstraction over floating point types +#[doc(hidden)] +pub trait Float: PartialOrd + PartialEq + private::FloatImpl {} +impl Float for f32 {} +impl Float for f64 {} + +// Keep this trait in private module, to avoid exposing its methods as extensions in user code +mod private { + use super::*; + + pub trait FloatImpl { + fn is_nan(&self) -> bool; + fn hash(&self, state: &mut H); + } + + impl FloatImpl for f32 { + #[inline] + fn is_nan(&self) -> bool { + f32::is_nan(*self) + } + + #[inline] + fn hash(&self, state: &mut H) { + crate::f32_hash(state, *self); + } + } + + impl FloatImpl for f64 { + #[inline] + fn is_nan(&self) -> bool { + f64::is_nan(*self) + } + + #[inline] + fn hash(&self, state: &mut H) { + crate::f64_hash(state, *self); + } + } +}