From: Joshua Simmons Date: Sun, 26 Feb 2023 18:17:50 +0000 (+0100) Subject: Add FiniteF32 and FiniteF64 wrappers X-Git-Url: https://git.nega.tv//gitweb.cgi?a=commitdiff_plain;h=b1f0ae609514288a9ba1b52bc850d4bdef69ddef;p=josh%2Fnarcissus Add FiniteF32 and FiniteF64 wrappers Ensures at creation time that the floating point values are neither NaN nor infinities, so that we can implement `Hash`, `Eq` and `Ord` traits. --- diff --git a/libs/narcissus-core/src/finite.rs b/libs/narcissus-core/src/finite.rs new file mode 100644 index 0000000..1ef3676 --- /dev/null +++ b/libs/narcissus-core/src/finite.rs @@ -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 { + 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(&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 { + 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(&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); + } +} diff --git a/libs/narcissus-core/src/lib.rs b/libs/narcissus-core/src/lib.rs index 1453b4e..038ddd1 100644 --- a/libs/narcissus-core/src/lib.rs +++ b/libs/narcissus-core/src/lib.rs @@ -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]