cubecl_common/float/fp8/
fp8_e8m0.rs1use core::fmt::Display;
2
3use bytemuck::{Pod, Zeroable};
4
5#[allow(non_camel_case_types)]
10#[repr(transparent)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12#[derive(Clone, Copy, Default, Zeroable, Pod, PartialEq, PartialOrd, Debug)]
13pub struct ue8m0(u8);
14
15impl ue8m0 {
16 pub const MAX: ue8m0 = ue8m0(0xfe);
18 pub const MIN: ue8m0 = ue8m0(0);
20
21 #[inline]
23 #[must_use]
24 pub const fn from_bits(bits: u8) -> ue8m0 {
25 ue8m0(bits)
26 }
27
28 #[inline]
33 #[must_use]
34 pub const fn from_f32(value: f32) -> ue8m0 {
35 Self::from_f64(value as f64)
36 }
37
38 #[inline]
43 #[must_use]
44 pub const fn from_f64(value: f64) -> ue8m0 {
45 if value.is_nan() {
46 return ue8m0(0xff);
47 }
48 if value <= 0.0 {
49 return Self::MIN;
50 }
51 let bits = value.to_bits();
52 let exponent = ((bits >> 52) & 0x7ff) as i32 - 1023;
53 let log2_up = exponent + (bits & ((1 << 52) - 1) != 0) as i32;
55 match log2_up + 127 {
56 biased if biased < 0 => Self::MIN,
57 biased if biased > 0xfe => Self::MAX,
58 biased => ue8m0(biased as u8),
59 }
60 }
61
62 #[inline]
64 #[must_use]
65 pub const fn to_bits(self) -> u8 {
66 self.0
67 }
68
69 #[inline]
73 #[must_use]
74 pub const fn to_f32(self) -> f32 {
75 self.to_f64() as f32
76 }
77
78 #[inline]
82 #[must_use]
83 pub const fn to_f64(self) -> f64 {
84 match self.0 {
85 0xff => f64::NAN,
86 code => f64::from_bits((code as u64 + 1023 - 127) << 52),
88 }
89 }
90}
91
92impl Display for ue8m0 {
93 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
94 write!(f, "ue8m0({:x})", self.0)
95 }
96}
97
98mod numeric {
99 use num_traits::{NumCast, ToPrimitive};
100
101 use super::*;
102 use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
103
104 impl Neg for ue8m0 {
105 type Output = Self;
106
107 fn neg(self) -> Self::Output {
108 Self::from_f32(self.to_f32().neg())
109 }
110 }
111
112 impl Mul for ue8m0 {
113 type Output = Self;
114
115 fn mul(self, rhs: Self) -> Self::Output {
116 Self::from_f32(self.to_f32() * rhs.to_f32())
117 }
118 }
119
120 impl MulAssign for ue8m0 {
121 fn mul_assign(&mut self, rhs: Self) {
122 *self = *self * rhs;
123 }
124 }
125
126 impl Div for ue8m0 {
127 type Output = Self;
128
129 fn div(self, rhs: Self) -> Self::Output {
130 Self::from_f32(self.to_f32() / rhs.to_f32())
131 }
132 }
133
134 impl DivAssign for ue8m0 {
135 fn div_assign(&mut self, rhs: Self) {
136 *self = *self / rhs;
137 }
138 }
139
140 impl Add for ue8m0 {
141 type Output = Self;
142
143 fn add(self, rhs: Self) -> Self::Output {
144 Self::from_f32(self.to_f32() + rhs.to_f32())
145 }
146 }
147
148 impl AddAssign for ue8m0 {
149 fn add_assign(&mut self, rhs: Self) {
150 *self = *self + rhs;
151 }
152 }
153
154 impl Sub for ue8m0 {
155 type Output = Self;
156
157 fn sub(self, rhs: Self) -> Self::Output {
158 Self::from_f32(self.to_f32() - rhs.to_f32())
159 }
160 }
161
162 impl SubAssign for ue8m0 {
163 fn sub_assign(&mut self, rhs: Self) {
164 *self = *self - rhs;
165 }
166 }
167
168 impl ToPrimitive for ue8m0 {
169 fn to_i64(&self) -> Option<i64> {
170 Some(ue8m0::to_f32(*self) as i64)
171 }
172
173 fn to_u64(&self) -> Option<u64> {
174 Some(ue8m0::to_f64(*self) as u64)
175 }
176
177 fn to_f32(&self) -> Option<f32> {
178 Some(ue8m0::to_f32(*self))
179 }
180
181 fn to_f64(&self) -> Option<f64> {
182 Some(ue8m0::to_f64(*self))
183 }
184 }
185
186 impl NumCast for ue8m0 {
187 fn from<T: num_traits::ToPrimitive>(n: T) -> Option<Self> {
188 Some(Self::from_f32(n.to_f32()?))
189 }
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn every_code_converts_to_its_value_and_back() {
199 for code in 0..=0xfeu8 {
200 let value = ue8m0::from_bits(code).to_f32();
201 assert_eq!(
202 ue8m0::from_f32(value).to_bits(),
203 code,
204 "{code:#x} is {value:e}"
205 );
206 }
207 assert!(ue8m0::from_bits(0xff).to_f32().is_nan());
208 }
209
210 #[test]
211 fn a_value_between_powers_of_two_rounds_up() {
212 assert_eq!(ue8m0::from_f32(3.0).to_f32(), 4.0);
213 assert_eq!(ue8m0::from_f64(1.0 + f64::EPSILON).to_f64(), 2.0);
214 assert_eq!(ue8m0::from_f64(4.0 * (1.0 + f64::EPSILON)).to_f64(), 8.0);
215 let above_min = ue8m0::MIN.to_f64() * (1.0 + f64::EPSILON);
216 assert_eq!(ue8m0::from_f64(above_min).to_bits(), 1);
217 assert_eq!(ue8m0::from_f32(-1.0), ue8m0::MIN);
218 }
219
220 #[test]
221 fn nan_is_the_all_ones_code() {
222 assert_eq!(ue8m0::from_f32(f32::NAN).to_bits(), 0xff);
223 }
224}