1#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub enum DType {
11 F32,
12 F16,
13 BF16,
14 F64,
15 I8,
16 I16,
17 I32,
18 I64,
19 U8,
20 U32,
21 Bool,
22 C64,
33 C128,
41}
42
43impl DType {
44 pub const fn size_bytes(self) -> usize {
46 match self {
47 Self::Bool | Self::I8 | Self::U8 => 1,
48 Self::F16 | Self::BF16 | Self::I16 => 2,
49 Self::F32 | Self::I32 | Self::U32 => 4,
50 Self::F64 | Self::I64 | Self::C64 => 8,
51 Self::C128 => 16,
52 }
53 }
54
55 pub const fn is_float(self) -> bool {
56 matches!(self, Self::F32 | Self::F16 | Self::BF16 | Self::F64)
57 }
58
59 pub const fn is_complex(self) -> bool {
63 matches!(self, Self::C64 | Self::C128)
64 }
65
66 pub const fn is_int(self) -> bool {
67 matches!(
68 self,
69 Self::I8 | Self::I16 | Self::I32 | Self::I64 | Self::U8 | Self::U32
70 )
71 }
72
73 pub const fn promotion_rank(self) -> u8 {
86 match self {
87 Self::Bool => 0,
88 Self::U8 | Self::I8 => 1,
89 Self::I16 | Self::BF16 => 2,
90 Self::F16 => 3,
91 Self::U32 | Self::I32 => 4,
92 Self::I64 => 5,
93 Self::F32 => 6,
94 Self::F64 => 7,
95 Self::C64 => 8,
96 Self::C128 => 9,
99 }
100 }
101
102 pub fn promote(self, other: Self) -> Self {
106 if self == other {
107 return self;
108 }
109 if matches!(
112 (self, other),
113 (Self::F16, Self::BF16) | (Self::BF16, Self::F16)
114 ) {
115 return Self::F32;
116 }
117 let promote_int_to_float = |int: Self, float: Self| -> Self {
119 match (int, float) {
120 (_, Self::F64) => Self::F64,
121 (Self::I64, _) => Self::F64, (_, Self::F32) => Self::F32,
123 (_, Self::F16) | (_, Self::BF16) => Self::F32, _ => float,
125 }
126 };
127 match (
128 self.is_int(),
129 other.is_int(),
130 self.is_float(),
131 other.is_float(),
132 ) {
133 (true, false, false, true) => promote_int_to_float(self, other),
134 (false, true, true, false) => promote_int_to_float(other, self),
135 _ => {
136 if self.promotion_rank() >= other.promotion_rank() {
137 self
138 } else {
139 other
140 }
141 }
142 }
143 }
144}
145
146fn integral_scalar(value: f64, name: &str) -> Result<i64, String> {
147 if !value.is_finite() {
148 return Err(format!(
149 "constant value {value} is not finite for dtype {name}"
150 ));
151 }
152 if value.fract() != 0.0 {
153 return Err(format!(
154 "constant value {value} must be integral for dtype {name}"
155 ));
156 }
157 Ok(value as i64)
158}
159
160pub fn scalar_constant_bytes(value: f64, dtype: DType) -> Result<Vec<u8>, String> {
162 let out_of_range =
163 |name: &str| format!("constant value {value} is out of range for dtype {name}");
164 match dtype {
165 DType::F32 => Ok((value as f32).to_le_bytes().to_vec()),
166 DType::F64 => Ok(value.to_le_bytes().to_vec()),
167 DType::I8 => {
168 let v = integral_scalar(value, "i8")?;
169 if !(i8::MIN as i64..=i8::MAX as i64).contains(&v) {
170 return Err(out_of_range("i8"));
171 }
172 Ok((v as i8).to_le_bytes().to_vec())
173 }
174 DType::I16 => {
175 let v = integral_scalar(value, "i16")?;
176 if !(i16::MIN as i64..=i16::MAX as i64).contains(&v) {
177 return Err(out_of_range("i16"));
178 }
179 Ok((v as i16).to_le_bytes().to_vec())
180 }
181 DType::I32 => {
182 let v = integral_scalar(value, "i32")?;
183 if !(i32::MIN as i64..=i32::MAX as i64).contains(&v) {
184 return Err(out_of_range("i32"));
185 }
186 Ok((v as i32).to_le_bytes().to_vec())
187 }
188 DType::I64 => {
189 if !value.is_finite() {
190 return Err(format!(
191 "constant value {value} is not finite for dtype i64"
192 ));
193 }
194 if value.fract() != 0.0 {
195 return Err(format!(
196 "constant value {value} must be integral for dtype i64"
197 ));
198 }
199 if value >= 9.223372036854776e18 || value < -9.223372036854776e18 {
201 return Err(out_of_range("i64"));
202 }
203 Ok((value as i64).to_le_bytes().to_vec())
204 }
205 DType::U8 => {
206 let v = integral_scalar(value, "u8")?;
207 if !(0..=u8::MAX as i64).contains(&v) {
208 return Err(out_of_range("u8"));
209 }
210 Ok((v as u8).to_le_bytes().to_vec())
211 }
212 DType::U32 => {
213 let v = integral_scalar(value, "u32")?;
214 if v < 0 || v > u32::MAX as i64 {
215 return Err(out_of_range("u32"));
216 }
217 Ok((v as u32).to_le_bytes().to_vec())
218 }
219 DType::Bool => Ok(vec![u8::from(value != 0.0)]),
220 DType::F16 | DType::BF16 | DType::C64 | DType::C128 => Err(format!(
221 "scalar literal dtype '{dtype:?}' is built via f32 constant + cast"
222 )),
223 }
224}
225
226#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
237pub struct Element {
238 pub dtype: DType,
239 pub subtype: ElementSubtype,
242 pub saturating: bool,
245}
246
247#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
248pub enum ElementSubtype {
249 Standard,
250 Fp8E4m3,
253 Fp8E5m2,
256}
257
258impl Element {
259 pub const fn new(dtype: DType) -> Self {
260 Self {
261 dtype,
262 subtype: ElementSubtype::Standard,
263 saturating: false,
264 }
265 }
266 pub const fn fp8_e4m3() -> Self {
267 Self {
268 dtype: DType::U8,
269 subtype: ElementSubtype::Fp8E4m3,
270 saturating: true,
271 }
272 }
273 pub const fn fp8_e5m2() -> Self {
274 Self {
275 dtype: DType::U8,
276 subtype: ElementSubtype::Fp8E5m2,
277 saturating: true,
278 }
279 }
280 pub const fn saturating(self) -> Self {
281 Self {
282 saturating: true,
283 ..self
284 }
285 }
286}
287
288impl std::fmt::Display for DType {
289 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
290 match self {
291 Self::F32 => write!(f, "f32"),
292 Self::F16 => write!(f, "f16"),
293 Self::BF16 => write!(f, "bf16"),
294 Self::F64 => write!(f, "f64"),
295 Self::I8 => write!(f, "i8"),
296 Self::I16 => write!(f, "i16"),
297 Self::I32 => write!(f, "i32"),
298 Self::I64 => write!(f, "i64"),
299 Self::U8 => write!(f, "u8"),
300 Self::U32 => write!(f, "u32"),
301 Self::Bool => write!(f, "bool"),
302 Self::C64 => write!(f, "c64"),
303 Self::C128 => write!(f, "c128"),
304 }
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311
312 #[test]
313 fn element_constructors() {
314 let f = Element::new(DType::F32);
315 assert_eq!(f.dtype, DType::F32);
316 assert_eq!(f.subtype, ElementSubtype::Standard);
317 assert!(!f.saturating);
318
319 let e4 = Element::fp8_e4m3();
320 assert_eq!(e4.subtype, ElementSubtype::Fp8E4m3);
321 assert!(e4.saturating);
322 assert_eq!(e4.dtype, DType::U8);
323
324 let s = Element::new(DType::I32).saturating();
325 assert!(s.saturating);
326 assert_eq!(s.dtype, DType::I32);
327 }
328
329 #[test]
330 fn promote_same() {
331 assert_eq!(DType::F32.promote(DType::F32), DType::F32);
332 assert_eq!(DType::I8.promote(DType::I8), DType::I8);
333 }
334
335 #[test]
336 fn promote_int_widening() {
337 assert_eq!(DType::I8.promote(DType::I16), DType::I16);
338 assert_eq!(DType::I32.promote(DType::I64), DType::I64);
339 }
340
341 #[test]
342 fn promote_int_to_float() {
343 assert_eq!(DType::I32.promote(DType::F32), DType::F32);
344 assert_eq!(DType::I64.promote(DType::F32), DType::F64);
345 assert_eq!(DType::I8.promote(DType::F16), DType::F32);
346 }
347
348 #[test]
349 fn promote_f16_bf16_goes_to_f32() {
350 assert_eq!(DType::F16.promote(DType::BF16), DType::F32);
351 assert_eq!(DType::BF16.promote(DType::F16), DType::F32);
352 }
353
354 #[test]
355 fn promote_is_commutative_for_well_defined_pairs() {
356 let pairs = [
357 (DType::F32, DType::F16),
358 (DType::I32, DType::F64),
359 (DType::Bool, DType::I8),
360 ];
361 for (a, b) in pairs {
362 assert_eq!(
363 a.promote(b),
364 b.promote(a),
365 "promote({a},{b}) should equal promote({b},{a})"
366 );
367 }
368 }
369
370 #[test]
371 fn scalar_constant_bytes_round_trips() {
372 assert_eq!(
373 scalar_constant_bytes(2.5, DType::F32).unwrap(),
374 2.5f32.to_le_bytes().to_vec()
375 );
376 assert_eq!(
377 scalar_constant_bytes(-1.0, DType::F64).unwrap(),
378 (-1.0f64).to_le_bytes().to_vec()
379 );
380 assert_eq!(
381 scalar_constant_bytes(7.0, DType::I32).unwrap(),
382 7i32.to_le_bytes()
383 );
384 assert_eq!(scalar_constant_bytes(0.0, DType::Bool).unwrap(), vec![0]);
385 assert_eq!(scalar_constant_bytes(1.0, DType::Bool).unwrap(), vec![1]);
386 }
387
388 #[test]
389 fn scalar_constant_bytes_rejects_out_of_range() {
390 assert!(scalar_constant_bytes(128.0, DType::I8).is_err());
391 assert!(scalar_constant_bytes(-1.0, DType::U32).is_err());
392 assert!(scalar_constant_bytes(9.223372036854776e18, DType::I64).is_err());
393 assert!(scalar_constant_bytes(2.5, DType::I32).is_err());
394 }
395
396 #[test]
397 fn scalar_constant_bytes_rejects_low_precision_direct() {
398 assert!(scalar_constant_bytes(1.0, DType::F16).is_err());
399 assert!(scalar_constant_bytes(1.0, DType::BF16).is_err());
400 assert!(scalar_constant_bytes(1.0, DType::C64).is_err());
401 assert!(scalar_constant_bytes(1.0, DType::C128).is_err());
402 }
403
404 #[test]
405 fn c128_size_and_flags() {
406 assert_eq!(DType::C128.size_bytes(), 16);
408 assert_eq!(DType::C64.size_bytes(), 8);
409 assert!(DType::C128.is_complex());
411 assert!(DType::C64.is_complex());
412 assert!(!DType::F64.is_complex());
413 assert!(!DType::C128.is_float());
415 assert!(!DType::C128.is_int());
416 assert_eq!(DType::C128.to_string(), "c128");
417 }
418
419 #[test]
420 fn c128_promotion() {
421 assert_eq!(DType::C64.promote(DType::C128), DType::C128);
423 assert_eq!(DType::C128.promote(DType::C64), DType::C128);
424 assert_eq!(DType::C128.promote(DType::C128), DType::C128);
425 assert!(DType::C128.promotion_rank() > DType::C64.promotion_rank());
426 }
427}