]> git.nega.tv - josh/narcissus/commitdiff
Add FiniteF32 and FiniteF64 wrappers
authorJoshua Simmons <josh@nega.tv>
Sun, 26 Feb 2023 18:17:50 +0000 (19:17 +0100)
committerJoshua Simmons <josh@nega.tv>
Sun, 26 Feb 2023 18:17:50 +0000 (19:17 +0100)
Ensures at creation time that the floating point values are neither NaN
nor infinities, so that we can implement `Hash`, `Eq` and `Ord` traits.

libs/narcissus-core/src/finite.rs [new file with mode: 0644]
libs/narcissus-core/src/lib.rs

diff --git a/libs/narcissus-core/src/finite.rs b/libs/narcissus-core/src/finite.rs
new file mode 100644 (file)
index 0000000..1ef3676
--- /dev/null
@@ -0,0 +1,98 @@
+use std::{error::Error, fmt::Display};
+
+#[derive(Debug)]
+pub struct NotFiniteError;
+
+impl Display for NotFiniteError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{self:?}")
+    }
+}
+
+impl Error for NotFiniteError {}
+
+/// A floating point value that is gauranteed to be finite.
+///
+/// This allows us to safely implement Hash, Eq and Ord.
+#[repr(transparent)]
+#[derive(Clone, Copy, PartialEq, PartialOrd)]
+pub struct FiniteF32(f32);
+
+impl FiniteF32 {
+    #[inline(always)]
+    pub fn new(x: f32) -> Result<FiniteF32, NotFiniteError> {
+        if x.is_finite() {
+            Ok(FiniteF32(x))
+        } else {
+            Err(NotFiniteError)
+        }
+    }
+
+    #[inline(always)]
+    pub fn get(self) -> f32 {
+        self.0
+    }
+}
+
+impl Eq for FiniteF32 {}
+
+impl Ord for FiniteF32 {
+    #[inline(always)]
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        // Safety: There are no NaNs since FiniteF32 is always finite.
+        unsafe { self.0.partial_cmp(&other.0).unwrap_unchecked() }
+    }
+}
+
+impl std::hash::Hash for FiniteF32 {
+    #[inline(always)]
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        // Hash requires that if `a == b` then `hash(a) == hash(b)`.
+        // In ieee 754 floating point `0.0 == -0.0`, so we must normalize the value before hashing.
+        let x = if self.0 == 0.0 { 0.0 } else { self.0 };
+        x.to_bits().hash(state);
+    }
+}
+
+/// A floating point value that is gauranteed to be finite.
+///
+/// This allows us to safely implement Hash, Eq and Ord.
+#[repr(transparent)]
+#[derive(Clone, Copy, PartialEq, PartialOrd)]
+pub struct FiniteF64(f64);
+
+impl FiniteF64 {
+    #[inline(always)]
+    pub fn new(x: f64) -> Result<FiniteF64, NotFiniteError> {
+        if x.is_finite() {
+            Ok(FiniteF64(x))
+        } else {
+            Err(NotFiniteError)
+        }
+    }
+
+    #[inline(always)]
+    pub fn get(self) -> f64 {
+        self.0
+    }
+}
+
+impl Eq for FiniteF64 {}
+
+impl Ord for FiniteF64 {
+    #[inline(always)]
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        // Safety: There are no NaNs since FiniteF32 is always finite.
+        unsafe { self.0.partial_cmp(&other.0).unwrap_unchecked() }
+    }
+}
+
+impl std::hash::Hash for FiniteF64 {
+    #[inline(always)]
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        // Hash requires that if `a == b` then `hash(a) == hash(b)`.
+        // In ieee 754 floating point `0.0 == -0.0`, so we must normalize the value before hashing.
+        let x = if self.0 == 0.0 { 0.0 } else { self.0 };
+        x.to_bits().hash(state);
+    }
+}
index 1453b4eb250dbd0df604d3b42ad790ad356e2f67..038ddd1f150046493a9dd00dc514f329a09f7986 100644 (file)
@@ -1,5 +1,6 @@
 mod arena;
 mod bitset;
+mod finite;
 mod fixed_vec;
 mod libc;
 pub mod manual_arc;
@@ -25,6 +26,8 @@ pub use uuid::Uuid;
 pub use virtual_mem::{virtual_commit, virtual_free, virtual_reserve};
 pub use virtual_vec::{VirtualDeque, VirtualVec};
 
+pub use finite::{FiniteF32, FiniteF64, NotFiniteError};
+
 use std::{ffi::CStr, mem::MaybeUninit};
 
 #[macro_export]