use core::fmt::{Debug, Display, Formatter};
use core::hash::{Hash, Hasher};
pub trait FloatBits: Copy + PartialEq + Debug + Display {
type Bits: Copy + Eq + Hash;
fn to_bits(self) -> Self::Bits;
fn is_finite(self) -> bool;
fn zero() -> Self;
}
macro_rules! impl_float_bits {
($ty:ty, $bits:ty) => {
impl FloatBits for $ty {
type Bits = $bits;
fn to_bits(self) -> $bits {
<$ty>::to_bits(self)
}
fn is_finite(self) -> bool {
<$ty>::is_finite(self)
}
fn zero() -> Self {
<$ty>::from_bits(0)
}
}
};
}
impl_float_bits!(f32, u32);
impl_float_bits!(f64, u64);
impl_float_bits!(half::f16, u16);
impl_float_bits!(half::bf16, u16);
#[derive(Clone, Copy, Debug)]
pub struct ComptimeFloat<F: FloatBits>(F);
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct InvalidComptimeFloat<F>(pub F);
impl<F: FloatBits> Display for InvalidComptimeFloat<F> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "invalid comptime float: {} is not finite", self.0)
}
}
impl<F: FloatBits> core::error::Error for InvalidComptimeFloat<F> {}
impl<F: FloatBits> ComptimeFloat<F> {
pub fn new(val: F) -> Result<Self, InvalidComptimeFloat<F>> {
if !val.is_finite() {
return Err(InvalidComptimeFloat(val));
}
let val = if val == F::zero() { F::zero() } else { val };
Ok(Self(val))
}
pub fn get(self) -> F {
self.0
}
}
impl<F: FloatBits> PartialEq for ComptimeFloat<F> {
fn eq(&self, other: &Self) -> bool {
self.0.to_bits() == other.0.to_bits()
}
}
impl<F: FloatBits> Eq for ComptimeFloat<F> {}
impl<F: FloatBits> Hash for ComptimeFloat<F> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.to_bits().hash(state);
}
}
impl<F: FloatBits> Display for ComptimeFloat<F> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
Display::fmt(&self.0, f)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::format;
#[test]
fn rejects_non_finite() {
assert!(ComptimeFloat::new(f32::NAN).is_err());
assert!(ComptimeFloat::new(f32::INFINITY).is_err());
assert!(ComptimeFloat::new(f32::NEG_INFINITY).is_err());
}
#[test]
fn accepts_finite() {
assert!(ComptimeFloat::new(1.5f32).is_ok());
}
#[derive(Default)]
struct TestHasher(u64);
impl Hasher for TestHasher {
fn finish(&self) -> u64 {
self.0
}
fn write(&mut self, bytes: &[u8]) {
self.0 = bytes.iter().fold(self.0, |hash, byte| {
(hash ^ *byte as u64).wrapping_mul(0x100000001b3)
});
}
}
fn hash_of<T: Hash>(val: &T) -> u64 {
let mut hasher = TestHasher::default();
val.hash(&mut hasher);
hasher.finish()
}
#[test]
fn negative_zero_equals_positive_zero() {
let neg = ComptimeFloat::new(-0.0f32).unwrap();
let pos = ComptimeFloat::new(0.0f32).unwrap();
assert_eq!(neg, pos);
assert_eq!(hash_of(&neg), hash_of(&pos));
}
#[test]
fn distinct_values_are_not_equal() {
let a = ComptimeFloat::new(1.0f32).unwrap();
let b = ComptimeFloat::new(2.0f32).unwrap();
assert_ne!(a, b);
}
#[test]
fn display_matches_inner_value() {
let val = ComptimeFloat::new(3.25f32).unwrap();
assert_eq!(format!("{}", val), "3.25");
}
}