1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
7pub enum Specials {
8 None,
11 SaturateOnOverflow,
14 InfinityOnOverflow,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
20pub enum SignEncoding {
21 Absent,
23 Magnitude,
26 TwosComplement,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
35pub struct FloatLayout {
36 pub exponent_bits: u8,
38 pub mantissa_bits: u8,
41 pub specials: Specials,
43 pub bias_override: Option<i16>,
46 pub sign: SignEncoding,
48}
49
50const fn pow2(exponent: i32) -> f32 {
51 match exponent {
52 -126..=127 => f32::from_bits(((exponent + 127) as u32) << 23),
53 -149..=-127 => f32::from_bits(1 << (exponent + 149)),
54 ..=-150 => 0.0,
55 _ => f32::INFINITY,
56 }
57}
58
59impl FloatLayout {
60 pub const E5M2: Self = Self::ieee_like(5, 2, Specials::InfinityOnOverflow);
61 pub const E5M2_SATURATING: Self = Self::ieee_like(5, 2, Specials::SaturateOnOverflow);
63 pub const F16: Self = Self::ieee_like(5, 10, Specials::InfinityOnOverflow);
65 pub const E2M1: Self = Self::ieee_like(2, 1, Specials::None);
66 pub const E2M2: Self = Self::ieee_like(2, 2, Specials::None);
68 pub const E3M2: Self = Self::ieee_like(3, 2, Specials::None);
70 pub const E2M3: Self = Self::ieee_like(2, 3, Specials::None);
71 pub const BF32: Self = Self::ieee_like(8, 23, Specials::InfinityOnOverflow);
72 pub const BF16: Self = Self::ieee_like(8, 7, Specials::InfinityOnOverflow);
73 pub const BF8: Self = Self::E5M2;
74 pub const BF4: Self = Self::E2M1;
75 pub const INT8: Self = Self::integer(8, SignEncoding::TwosComplement);
76 pub const UINT8: Self = Self::integer(8, SignEncoding::Absent);
77 pub const INT16: Self = Self::integer(16, SignEncoding::TwosComplement);
78 pub const UINT16: Self = Self::integer(16, SignEncoding::Absent);
79 pub const INT32: Self = Self::integer(32, SignEncoding::TwosComplement);
80 pub const UINT32: Self = Self::integer(32, SignEncoding::Absent);
81 pub const UINT1: Self = Self::integer(1, SignEncoding::Absent);
82 pub const UINT2: Self = Self::integer(2, SignEncoding::Absent);
83 pub const UINT3: Self = Self::integer(3, SignEncoding::Absent);
84 pub const UINT4: Self = Self::integer(4, SignEncoding::Absent);
85 pub const UINT5: Self = Self::integer(5, SignEncoding::Absent);
86 pub const UINT6: Self = Self::integer(6, SignEncoding::Absent);
87 pub const SIGN_MAGNITUDE_INT8: Self = Self::integer(8, SignEncoding::Magnitude);
89
90 const fn ieee_like(exponent_bits: u8, mantissa_bits: u8, specials: Specials) -> Self {
91 Self {
92 exponent_bits,
93 mantissa_bits,
94 specials,
95 bias_override: None,
96 sign: SignEncoding::Magnitude,
97 }
98 }
99
100 const fn integer(width: u8, sign: SignEncoding) -> Self {
101 let mantissa_bits = match sign {
102 SignEncoding::Absent => width,
103 SignEncoding::Magnitude | SignEncoding::TwosComplement => width - 1,
104 };
105 Self {
106 exponent_bits: 0,
107 mantissa_bits,
108 specials: Specials::None,
109 bias_override: Some(1 - mantissa_bits as i16),
110 sign,
111 }
112 }
113
114 #[must_use]
115 pub const fn bias(&self) -> i16 {
116 match self.bias_override {
117 Some(bias) => bias,
118 None => (1i16 << self.exponent_bits.saturating_sub(1)) - 1,
119 }
120 }
121
122 #[must_use]
123 pub const fn width(&self) -> u8 {
124 let sign_bits = match self.sign {
125 SignEncoding::Absent => 0,
126 SignEncoding::Magnitude | SignEncoding::TwosComplement => 1,
127 };
128 sign_bits + self.exponent_bits + self.mantissa_bits
129 }
130
131 #[must_use]
135 pub fn same_format(&self, other: &Self) -> bool {
136 self.exponent_bits == other.exponent_bits
137 && self.mantissa_bits == other.mantissa_bits
138 && self.specials == other.specials
139 && self.bias() == other.bias()
140 && self.sign == other.sign
141 }
142
143 #[must_use]
147 pub const fn packed_bytes(
148 &self,
149 elements: u64,
150 scale_block: Option<(FloatLayout, u64)>,
151 ) -> u64 {
152 let element_bits = elements * self.width() as u64;
153 let scale_bits = match scale_block {
154 Some((scale, block)) if block > 0 => elements.div_ceil(block) * scale.width() as u64,
155 _ => 0,
156 };
157 (element_bits + scale_bits).div_ceil(8)
158 }
159
160 #[must_use]
161 pub const fn max_normal(&self) -> f32 {
162 let exponent_codes = (1i32 << self.exponent_bits) - 1;
163 let top_code = match self.specials {
164 Specials::None => exponent_codes,
165 Specials::SaturateOnOverflow | Specials::InfinityOnOverflow => exponent_codes - 1,
166 };
167 let significand = 2.0 - pow2(-(self.mantissa_bits as i32));
168 significand * pow2(top_code - self.bias() as i32)
169 }
170
171 #[must_use]
172 pub const fn min_normal(&self) -> f32 {
173 pow2(1 - self.bias() as i32)
174 }
175
176 #[must_use]
177 pub const fn min_subnormal(&self) -> f32 {
178 pow2(1 - self.bias() as i32 - self.mantissa_bits as i32)
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 const CASES: [(FloatLayout, i16, u8, f32, f32, f32); 6] = [
187 (
188 FloatLayout::E5M2,
189 15,
190 8,
191 57344.0,
192 6.103_515_6e-5,
193 1.525_878_9e-5,
194 ),
195 (FloatLayout::E2M1, 1, 4, 6.0, 1.0, 0.5),
196 (FloatLayout::E3M2, 3, 6, 28.0, 0.25, 0.0625),
197 (FloatLayout::E2M3, 1, 6, 7.5, 1.0, 0.125),
198 (
199 FloatLayout::BF16,
200 127,
201 16,
202 3.389_531_4e38,
203 f32::MIN_POSITIVE,
204 f32::from_bits(1 << 16),
205 ),
206 (
207 FloatLayout::BF32,
208 127,
209 32,
210 f32::MAX,
211 f32::MIN_POSITIVE,
212 1.0e-45,
213 ),
214 ];
215
216 #[test]
217 fn float_layout_derived_bias_per_layout() {
218 let biases = CASES.map(|case| case.0.bias());
219 assert_eq!(biases, CASES.map(|case| case.1));
220 }
221
222 #[test]
223 fn float_layout_derived_width_per_layout() {
224 let widths = CASES.map(|case| case.0.width());
225 assert_eq!(widths, CASES.map(|case| case.2));
226 }
227
228 #[test]
229 fn float_layout_derived_max_normal_per_layout() {
230 let maxima = CASES.map(|case| case.0.max_normal());
231 assert_eq!(maxima, CASES.map(|case| case.3));
232 }
233
234 #[test]
235 fn float_layout_derived_min_normal_per_layout() {
236 let minima = CASES.map(|case| case.0.min_normal());
237 assert_eq!(minima, CASES.map(|case| case.4));
238 }
239
240 #[test]
241 fn float_layout_derived_min_subnormal_per_layout() {
242 let minima = CASES.map(|case| case.0.min_subnormal());
243 assert_eq!(minima, CASES.map(|case| case.5));
244 }
245
246 #[test]
247 fn float_layout_derived_bias_override_shifts_range() {
248 let shifted = FloatLayout {
249 bias_override: Some(4),
250 ..FloatLayout::E3M2
251 };
252
253 assert_eq!(shifted.bias(), 4);
254 assert_eq!(shifted.max_normal(), 14.0);
255 }
256
257 #[test]
258 fn float_layout_constants_bf8_equals_e5m2() {
259 assert_eq!(FloatLayout::BF8, FloatLayout::E5M2);
260 }
261
262 #[test]
263 fn float_layout_constants_bf4_equals_e2m1() {
264 assert_eq!(FloatLayout::BF4, FloatLayout::E2M1);
265 }
266
267 #[test]
268 fn float_layout_constants_bf32_is_e8m23() {
269 let layout = FloatLayout::BF32;
270
271 assert_eq!((layout.exponent_bits, layout.mantissa_bits), (8, 23));
272 assert_eq!(layout.max_normal(), f32::MAX);
273 assert_eq!(layout.min_subnormal().to_bits(), 1);
274 }
275
276 #[test]
277 fn float_layout_constants_f16_is_e5m10() {
278 let layout = FloatLayout::F16;
279
280 assert_eq!((layout.exponent_bits, layout.mantissa_bits), (5, 10));
281 assert_eq!(layout.max_normal(), 65504.0);
282 assert_eq!(layout.min_subnormal(), 2.0f32.powi(-24));
283 }
284
285 #[test]
286 fn float_layout_constants_bf16_is_e8m7() {
287 let layout = FloatLayout::BF16;
288
289 assert_eq!((layout.exponent_bits, layout.mantissa_bits), (8, 7));
290 assert_eq!(layout.max_normal().to_bits(), 0x7f7f_0000);
291 }
292
293 #[test]
294 fn float_layout_constants_specials_and_widths_per_layout() {
295 let observed = [
296 FloatLayout::E5M2,
297 FloatLayout::BF8,
298 FloatLayout::E5M2_SATURATING,
299 FloatLayout::BF16,
300 FloatLayout::BF32,
301 FloatLayout::E3M2,
302 FloatLayout::E2M3,
303 FloatLayout::E2M1,
304 FloatLayout::BF4,
305 ]
306 .map(|layout| (layout.specials, layout.width()));
307
308 let saturate = Specials::SaturateOnOverflow;
309 let infinity = Specials::InfinityOnOverflow;
310 let expected = [
311 (infinity, 8),
312 (infinity, 8),
313 (saturate, 8),
314 (infinity, 16),
315 (infinity, 32),
316 (Specials::None, 6),
317 (Specials::None, 6),
318 (Specials::None, 4),
319 (Specials::None, 4),
320 ];
321 assert_eq!(observed, expected);
322 }
323}