cubecl_common/float/
comptime_float.rs1use core::fmt::{Debug, Display, Formatter};
2use core::hash::{Hash, Hasher};
3
4pub trait FloatBits: Copy + PartialEq + Debug + Display {
10 type Bits: Copy + Eq + Hash;
12
13 fn to_bits(self) -> Self::Bits;
15
16 fn is_finite(self) -> bool;
18
19 fn zero() -> Self;
21}
22
23macro_rules! impl_float_bits {
24 ($ty:ty, $bits:ty) => {
25 impl FloatBits for $ty {
26 type Bits = $bits;
27
28 fn to_bits(self) -> $bits {
29 <$ty>::to_bits(self)
30 }
31
32 fn is_finite(self) -> bool {
33 <$ty>::is_finite(self)
34 }
35
36 fn zero() -> Self {
37 <$ty>::from_bits(0)
38 }
39 }
40 };
41}
42
43impl_float_bits!(f32, u32);
44impl_float_bits!(f64, u64);
45impl_float_bits!(half::f16, u16);
46impl_float_bits!(half::bf16, u16);
47
48#[derive(Clone, Copy, Debug)]
61pub struct ComptimeFloat<F: FloatBits>(F);
62
63#[derive(Debug, Clone, Copy, PartialEq)]
66pub struct InvalidComptimeFloat<F>(pub F);
67
68impl<F: FloatBits> Display for InvalidComptimeFloat<F> {
69 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
70 write!(f, "invalid comptime float: {} is not finite", self.0)
71 }
72}
73
74impl<F: FloatBits> core::error::Error for InvalidComptimeFloat<F> {}
75
76impl<F: FloatBits> ComptimeFloat<F> {
77 pub fn new(val: F) -> Result<Self, InvalidComptimeFloat<F>> {
79 if !val.is_finite() {
80 return Err(InvalidComptimeFloat(val));
81 }
82 let val = if val == F::zero() { F::zero() } else { val };
84 Ok(Self(val))
85 }
86
87 pub fn get(self) -> F {
89 self.0
90 }
91}
92
93impl<F: FloatBits> PartialEq for ComptimeFloat<F> {
94 fn eq(&self, other: &Self) -> bool {
95 self.0.to_bits() == other.0.to_bits()
96 }
97}
98
99impl<F: FloatBits> Eq for ComptimeFloat<F> {}
100
101impl<F: FloatBits> Hash for ComptimeFloat<F> {
102 fn hash<H: Hasher>(&self, state: &mut H) {
103 self.0.to_bits().hash(state);
104 }
105}
106
107impl<F: FloatBits> Display for ComptimeFloat<F> {
108 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
109 Display::fmt(&self.0, f)
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116 use alloc::format;
117
118 #[test]
119 fn rejects_non_finite() {
120 assert!(ComptimeFloat::new(f32::NAN).is_err());
121 assert!(ComptimeFloat::new(f32::INFINITY).is_err());
122 assert!(ComptimeFloat::new(f32::NEG_INFINITY).is_err());
123 }
124
125 #[test]
126 fn accepts_finite() {
127 assert!(ComptimeFloat::new(1.5f32).is_ok());
128 }
129
130 #[derive(Default)]
132 struct TestHasher(u64);
133
134 impl Hasher for TestHasher {
135 fn finish(&self) -> u64 {
136 self.0
137 }
138
139 fn write(&mut self, bytes: &[u8]) {
140 self.0 = bytes.iter().fold(self.0, |hash, byte| {
141 (hash ^ *byte as u64).wrapping_mul(0x100000001b3)
142 });
143 }
144 }
145
146 fn hash_of<T: Hash>(val: &T) -> u64 {
147 let mut hasher = TestHasher::default();
148 val.hash(&mut hasher);
149 hasher.finish()
150 }
151
152 #[test]
153 fn negative_zero_equals_positive_zero() {
154 let neg = ComptimeFloat::new(-0.0f32).unwrap();
155 let pos = ComptimeFloat::new(0.0f32).unwrap();
156 assert_eq!(neg, pos);
157 assert_eq!(hash_of(&neg), hash_of(&pos));
158 }
159
160 #[test]
161 fn distinct_values_are_not_equal() {
162 let a = ComptimeFloat::new(1.0f32).unwrap();
163 let b = ComptimeFloat::new(2.0f32).unwrap();
164 assert_ne!(a, b);
165 }
166
167 #[test]
168 fn display_matches_inner_value() {
169 let val = ComptimeFloat::new(3.25f32).unwrap();
170 assert_eq!(format!("{}", val), "3.25");
171 }
172}