vortex_scalar/
pvalue.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use core::fmt::Display;
5use std::cmp::Ordering;
6use std::hash::{Hash, Hasher};
7
8use num_traits::NumCast;
9use paste::paste;
10use vortex_dtype::half::f16;
11use vortex_dtype::{NativePType, PType, ToBytes};
12use vortex_error::{VortexError, VortexExpect, VortexResult, vortex_bail, vortex_err};
13
14/// A primitive value that can represent any primitive type supported by Vortex.
15///
16/// `PValue` is used to store primitive scalar values in a type-erased manner,
17/// supporting all primitive types (integers, floats) with various bit widths.
18#[derive(Debug, Clone, Copy)]
19pub enum PValue {
20    /// Unsigned 8-bit integer.
21    U8(u8),
22    /// Unsigned 16-bit integer.
23    U16(u16),
24    /// Unsigned 32-bit integer.
25    U32(u32),
26    /// Unsigned 64-bit integer.
27    U64(u64),
28    /// Signed 8-bit integer.
29    I8(i8),
30    /// Signed 16-bit integer.
31    I16(i16),
32    /// Signed 32-bit integer.
33    I32(i32),
34    /// Signed 64-bit integer.
35    I64(i64),
36    /// 16-bit floating point.
37    F16(f16),
38    /// 32-bit floating point.
39    F32(f32),
40    /// 64-bit floating point.
41    F64(f64),
42}
43
44impl PartialEq for PValue {
45    fn eq(&self, other: &Self) -> bool {
46        match (self, other) {
47            (Self::U8(s), o) => o.as_u64().vortex_expect("upcast") == *s as u64,
48            (Self::U16(s), o) => o.as_u64().vortex_expect("upcast") == *s as u64,
49            (Self::U32(s), o) => o.as_u64().vortex_expect("upcast") == *s as u64,
50            (Self::U64(s), o) => o.as_u64().vortex_expect("upcast") == *s,
51            (Self::I8(s), o) => o.as_i64().vortex_expect("upcast") == *s as i64,
52            (Self::I16(s), o) => o.as_i64().vortex_expect("upcast") == *s as i64,
53            (Self::I32(s), o) => o.as_i64().vortex_expect("upcast") == *s as i64,
54            (Self::I64(s), o) => o.as_i64().vortex_expect("upcast") == *s,
55            (Self::F16(s), Self::F16(o)) => s.is_eq(*o),
56            (Self::F32(s), Self::F32(o)) => s.is_eq(*o),
57            (Self::F64(s), Self::F64(o)) => s.is_eq(*o),
58            (..) => false,
59        }
60    }
61}
62
63impl Eq for PValue {}
64
65impl PartialOrd for PValue {
66    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
67        match (self, other) {
68            (Self::U8(s), o) => Some((*s as u64).cmp(&o.as_u64().vortex_expect("upcast"))),
69            (Self::U16(s), o) => Some((*s as u64).cmp(&o.as_u64().vortex_expect("upcast"))),
70            (Self::U32(s), o) => Some((*s as u64).cmp(&o.as_u64().vortex_expect("upcast"))),
71            (Self::U64(s), o) => Some((*s).cmp(&o.as_u64().vortex_expect("upcast"))),
72            (Self::I8(s), o) => Some((*s as i64).cmp(&o.as_i64().vortex_expect("upcast"))),
73            (Self::I16(s), o) => Some((*s as i64).cmp(&o.as_i64().vortex_expect("upcast"))),
74            (Self::I32(s), o) => Some((*s as i64).cmp(&o.as_i64().vortex_expect("upcast"))),
75            (Self::I64(s), o) => Some((*s).cmp(&o.as_i64().vortex_expect("upcast"))),
76            (Self::F16(s), Self::F16(o)) => Some(s.total_compare(*o)),
77            (Self::F32(s), Self::F32(o)) => Some(s.total_compare(*o)),
78            (Self::F64(s), Self::F64(o)) => Some(s.total_compare(*o)),
79            (..) => None,
80        }
81    }
82}
83
84impl Hash for PValue {
85    fn hash<H: Hasher>(&self, state: &mut H) {
86        match self {
87            PValue::U8(_) | PValue::U16(_) | PValue::U32(_) | PValue::U64(_) => {
88                self.as_u64().vortex_expect("upcast").hash(state)
89            }
90            PValue::I8(_) | PValue::I16(_) | PValue::I32(_) | PValue::I64(_) => {
91                self.as_i64().vortex_expect("upcast").hash(state)
92            }
93            PValue::F16(v) => v.to_le_bytes().hash(state),
94            PValue::F32(v) => v.to_le_bytes().hash(state),
95            PValue::F64(v) => v.to_le_bytes().hash(state),
96        }
97    }
98}
99
100impl ToBytes for PValue {
101    fn to_le_bytes(&self) -> &[u8] {
102        match self {
103            PValue::U8(v) => v.to_le_bytes(),
104            PValue::U16(v) => v.to_le_bytes(),
105            PValue::U32(v) => v.to_le_bytes(),
106            PValue::U64(v) => v.to_le_bytes(),
107            PValue::I8(v) => v.to_le_bytes(),
108            PValue::I16(v) => v.to_le_bytes(),
109            PValue::I32(v) => v.to_le_bytes(),
110            PValue::I64(v) => v.to_le_bytes(),
111            PValue::F16(v) => v.to_le_bytes(),
112            PValue::F32(v) => v.to_le_bytes(),
113            PValue::F64(v) => v.to_le_bytes(),
114        }
115    }
116}
117
118macro_rules! as_primitive {
119    ($T:ty, $PT:tt) => {
120        paste! {
121            #[doc = "Access PValue as `" $T "`, returning `None` if conversion is unsuccessful"]
122            pub fn [<as_ $T>](self) -> Option<$T> {
123                match self {
124                    PValue::U8(v) => <$T as NumCast>::from(v),
125                    PValue::U16(v) => <$T as NumCast>::from(v),
126                    PValue::U32(v) => <$T as NumCast>::from(v),
127                    PValue::U64(v) => <$T as NumCast>::from(v),
128                    PValue::I8(v) => <$T as NumCast>::from(v),
129                    PValue::I16(v) => <$T as NumCast>::from(v),
130                    PValue::I32(v) => <$T as NumCast>::from(v),
131                    PValue::I64(v) => <$T as NumCast>::from(v),
132                    PValue::F16(v) => <$T as NumCast>::from(v),
133                    PValue::F32(v) => <$T as NumCast>::from(v),
134                    PValue::F64(v) => <$T as NumCast>::from(v),
135                }
136            }
137        }
138    };
139}
140
141impl PValue {
142    /// Creates a zero value for the given primitive type.
143    pub fn zero(ptype: PType) -> PValue {
144        match ptype {
145            PType::U8 => PValue::U8(0),
146            PType::U16 => PValue::U16(0),
147            PType::U32 => PValue::U32(0),
148            PType::U64 => PValue::U64(0),
149            PType::I8 => PValue::I8(0),
150            PType::I16 => PValue::I16(0),
151            PType::I32 => PValue::I32(0),
152            PType::I64 => PValue::I64(0),
153            PType::F16 => PValue::F16(f16::from_f32(0.0)),
154            PType::F32 => PValue::F32(0.0),
155            PType::F64 => PValue::F64(0.0),
156        }
157    }
158
159    /// Returns the primitive type of this value.
160    pub fn ptype(&self) -> PType {
161        match self {
162            Self::U8(_) => PType::U8,
163            Self::U16(_) => PType::U16,
164            Self::U32(_) => PType::U32,
165            Self::U64(_) => PType::U64,
166            Self::I8(_) => PType::I8,
167            Self::I16(_) => PType::I16,
168            Self::I32(_) => PType::I32,
169            Self::I64(_) => PType::I64,
170            Self::F16(_) => PType::F16,
171            Self::F32(_) => PType::F32,
172            Self::F64(_) => PType::F64,
173        }
174    }
175
176    /// Returns true if this value is of the given primitive type.
177    pub fn is_instance_of(&self, ptype: &PType) -> bool {
178        &self.ptype() == ptype
179    }
180
181    /// Converts this value to a specific native primitive type.
182    ///
183    /// Returns an error if the conversion is not supported or would overflow.
184    #[inline]
185    pub fn as_primitive<T: NativePType + TryFrom<PValue, Error = VortexError>>(
186        &self,
187    ) -> Result<T, VortexError> {
188        T::try_from(*self)
189    }
190
191    /// Reinterprets the bits of this value as a different primitive type.
192    ///
193    /// This performs a bitwise cast between types of the same width.
194    ///
195    /// # Panics
196    ///
197    /// Panics if the target type has a different byte width than this value.
198    pub fn reinterpret_cast(&self, ptype: PType) -> Self {
199        if ptype == self.ptype() {
200            return *self;
201        }
202
203        assert_eq!(
204            ptype.byte_width(),
205            self.ptype().byte_width(),
206            "Cannot reinterpret cast between types of different widths"
207        );
208
209        match self {
210            PValue::U8(v) => u8::cast_signed(*v).into(),
211            PValue::U16(v) => match ptype {
212                PType::I16 => u16::cast_signed(*v).into(),
213                PType::F16 => f16::from_bits(*v).into(),
214                _ => unreachable!("Only same width type are allowed to be reinterpreted"),
215            },
216            PValue::U32(v) => match ptype {
217                PType::I32 => u32::cast_signed(*v).into(),
218                PType::F32 => f32::from_bits(*v).into(),
219                _ => unreachable!("Only same width type are allowed to be reinterpreted"),
220            },
221            PValue::U64(v) => match ptype {
222                PType::I64 => u64::cast_signed(*v).into(),
223                PType::F64 => f64::from_bits(*v).into(),
224                _ => unreachable!("Only same width type are allowed to be reinterpreted"),
225            },
226            PValue::I8(v) => i8::cast_unsigned(*v).into(),
227            PValue::I16(v) => match ptype {
228                PType::U16 => i16::cast_unsigned(*v).into(),
229                PType::F16 => f16::from_bits(v.cast_unsigned()).into(),
230                _ => unreachable!("Only same width type are allowed to be reinterpreted"),
231            },
232            PValue::I32(v) => match ptype {
233                PType::U32 => i32::cast_unsigned(*v).into(),
234                PType::F32 => f32::from_bits(i32::cast_unsigned(*v)).into(),
235                _ => unreachable!("Only same width type are allowed to be reinterpreted"),
236            },
237            PValue::I64(v) => match ptype {
238                PType::U64 => i64::cast_unsigned(*v).into(),
239                PType::F64 => f64::from_bits(i64::cast_unsigned(*v)).into(),
240                _ => unreachable!("Only same width type are allowed to be reinterpreted"),
241            },
242            PValue::F16(v) => match ptype {
243                PType::U16 => v.to_bits().into(),
244                PType::I16 => v.to_bits().cast_signed().into(),
245                _ => unreachable!("Only same width type are allowed to be reinterpreted"),
246            },
247            PValue::F32(v) => match ptype {
248                PType::U32 => f32::to_bits(*v).into(),
249                PType::I32 => f32::to_bits(*v).cast_signed().into(),
250                _ => unreachable!("Only same width type are allowed to be reinterpreted"),
251            },
252            PValue::F64(v) => match ptype {
253                PType::U64 => f64::to_bits(*v).into(),
254                PType::I64 => f64::to_bits(*v).cast_signed().into(),
255                _ => unreachable!("Only same width type are allowed to be reinterpreted"),
256            },
257        }
258    }
259
260    as_primitive!(i8, I8);
261    as_primitive!(i16, I16);
262    as_primitive!(i32, I32);
263    as_primitive!(i64, I64);
264    as_primitive!(u8, U8);
265    as_primitive!(u16, U16);
266    as_primitive!(u32, U32);
267    as_primitive!(u64, U64);
268    as_primitive!(f16, F16);
269    as_primitive!(f32, F32);
270    as_primitive!(f64, F64);
271}
272
273macro_rules! int_pvalue {
274    ($T:ty, $PT:tt) => {
275        impl TryFrom<PValue> for $T {
276            type Error = VortexError;
277
278            fn try_from(value: PValue) -> Result<Self, Self::Error> {
279                match value {
280                    PValue::U8(v) => <$T as NumCast>::from(v),
281                    PValue::U16(v) => <$T as NumCast>::from(v),
282                    PValue::U32(v) => <$T as NumCast>::from(v),
283                    PValue::U64(v) => <$T as NumCast>::from(v),
284                    PValue::I8(v) => <$T as NumCast>::from(v),
285                    PValue::I16(v) => <$T as NumCast>::from(v),
286                    PValue::I32(v) => <$T as NumCast>::from(v),
287                    PValue::I64(v) => <$T as NumCast>::from(v),
288                    _ => None,
289                }
290                .ok_or_else(|| {
291                    vortex_err!("Cannot read primitive value {:?} as {}", value, PType::$PT)
292                })
293            }
294        }
295    };
296}
297
298macro_rules! float_pvalue {
299    ($T:ty, $PT:tt) => {
300        impl TryFrom<PValue> for $T {
301            type Error = VortexError;
302
303            fn try_from(value: PValue) -> Result<Self, Self::Error> {
304                match value {
305                    PValue::U8(u) => <Self as NumCast>::from(u),
306                    PValue::U16(u) => <Self as NumCast>::from(u),
307                    PValue::U32(u) => <Self as NumCast>::from(u),
308                    PValue::U64(u) => <Self as NumCast>::from(u),
309                    PValue::I8(i) => <Self as NumCast>::from(i),
310                    PValue::I16(i) => <Self as NumCast>::from(i),
311                    PValue::I32(i) => <Self as NumCast>::from(i),
312                    PValue::I64(i) => <Self as NumCast>::from(i),
313                    PValue::F16(f) => <Self as NumCast>::from(f),
314                    PValue::F32(f) => <Self as NumCast>::from(f),
315                    PValue::F64(f) => <Self as NumCast>::from(f),
316                }
317                .ok_or_else(|| {
318                    vortex_err!("Cannot read primitive value {:?} as {}", value, PType::$PT)
319                })
320            }
321        }
322    };
323}
324
325int_pvalue!(u8, U8);
326int_pvalue!(u16, U16);
327int_pvalue!(u32, U32);
328int_pvalue!(u64, U64);
329int_pvalue!(usize, U64);
330int_pvalue!(i8, I8);
331int_pvalue!(i16, I16);
332int_pvalue!(i32, I32);
333int_pvalue!(i64, I64);
334
335float_pvalue!(f16, F16);
336float_pvalue!(f32, F32);
337float_pvalue!(f64, F64);
338
339macro_rules! impl_pvalue {
340    ($T:ty, $PT:tt) => {
341        impl From<$T> for PValue {
342            fn from(value: $T) -> Self {
343                PValue::$PT(value)
344            }
345        }
346    };
347}
348
349impl_pvalue!(u8, U8);
350impl_pvalue!(u16, U16);
351impl_pvalue!(u32, U32);
352impl_pvalue!(u64, U64);
353impl_pvalue!(i8, I8);
354impl_pvalue!(i16, I16);
355impl_pvalue!(i32, I32);
356impl_pvalue!(i64, I64);
357impl_pvalue!(f16, F16);
358impl_pvalue!(f32, F32);
359impl_pvalue!(f64, F64);
360
361impl From<usize> for PValue {
362    #[inline]
363    fn from(value: usize) -> PValue {
364        PValue::U64(value as u64)
365    }
366}
367
368impl Display for PValue {
369    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
370        match self {
371            Self::U8(v) => write!(f, "{v}u8"),
372            Self::U16(v) => write!(f, "{v}u16"),
373            Self::U32(v) => write!(f, "{v}u32"),
374            Self::U64(v) => write!(f, "{v}u64"),
375            Self::I8(v) => write!(f, "{v}i8"),
376            Self::I16(v) => write!(f, "{v}i16"),
377            Self::I32(v) => write!(f, "{v}i32"),
378            Self::I64(v) => write!(f, "{v}i64"),
379            Self::F16(v) => write!(f, "{v}f16"),
380            Self::F32(v) => write!(f, "{v}f32"),
381            Self::F64(v) => write!(f, "{v}f64"),
382        }
383    }
384}
385
386pub(super) trait CoercePValue: Sized {
387    /// Coerce value from a compatible bit representation using into given type.
388    ///
389    /// Integers can be widened from narrower type
390    /// Floats stored as integers will be reinterpreted as bit representation of the float
391    fn coerce(value: PValue) -> VortexResult<Self>;
392}
393
394macro_rules! int_coerce {
395    ($T:ty) => {
396        impl CoercePValue for $T {
397            fn coerce(value: PValue) -> VortexResult<Self> {
398                Self::try_from(value)
399            }
400        }
401    };
402}
403
404int_coerce!(u8);
405int_coerce!(u16);
406int_coerce!(u32);
407int_coerce!(u64);
408int_coerce!(i8);
409int_coerce!(i16);
410int_coerce!(i32);
411int_coerce!(i64);
412
413impl CoercePValue for f16 {
414    #[allow(clippy::cast_possible_truncation)]
415    fn coerce(value: PValue) -> VortexResult<Self> {
416        // F16 coercion behavior:
417        // - U8/U16/U32/U64: Interpreted as the bit representation of an f16 value.
418        //   Only the lower 16 bits are used, allowing compact storage of f16 values
419        //   as integers when the full type information is preserved externally.
420        // - F16: Passthrough
421        // - F32/F64: Numeric conversion with potential precision loss
422        // - Other types: Not supported
423        //
424        // Note: This bit-pattern interpretation means that integer value 0x3C00u16
425        // would be interpreted as f16(1.0), not as f16(15360.0).
426        match value {
427            PValue::U8(u) => Ok(Self::from_bits(u as u16)),
428            PValue::U16(u) => Ok(Self::from_bits(u)),
429            PValue::U32(u) => Ok(Self::from_bits(u as u16)),
430            PValue::U64(u) => Ok(Self::from_bits(u as u16)),
431            PValue::F16(u) => Ok(u),
432            PValue::F32(f) => {
433                <Self as NumCast>::from(f).ok_or_else(|| vortex_err!("Cannot convert f32 to f16"))
434            }
435            PValue::F64(f) => {
436                <Self as NumCast>::from(f).ok_or_else(|| vortex_err!("Cannot convert f64 to f16"))
437            }
438            _ => vortex_bail!("Cannot coerce {value:?} to f16: type not supported for coercion"),
439        }
440    }
441}
442
443impl CoercePValue for f32 {
444    #[allow(clippy::cast_possible_truncation)]
445    fn coerce(value: PValue) -> VortexResult<Self> {
446        // F32 coercion: U32 values are interpreted as bit patterns, not numeric conversions
447        match value {
448            PValue::U8(u) => Ok(Self::from_bits(u as u32)),
449            PValue::U16(u) => Ok(Self::from_bits(u as u32)),
450            PValue::U32(u) => Ok(Self::from_bits(u)),
451            PValue::U64(u) => Ok(Self::from_bits(u as u32)),
452            PValue::F16(f) => {
453                <Self as NumCast>::from(f).ok_or_else(|| vortex_err!("Cannot convert f16 to f32"))
454            }
455            PValue::F32(f) => Ok(f),
456            PValue::F64(f) => {
457                <Self as NumCast>::from(f).ok_or_else(|| vortex_err!("Cannot convert f64 to f32"))
458            }
459            _ => vortex_bail!("Unsupported PValue {value:?} type for f32"),
460        }
461    }
462}
463
464impl CoercePValue for f64 {
465    fn coerce(value: PValue) -> VortexResult<Self> {
466        // F64 coercion: U64 values are interpreted as bit patterns, not numeric conversions
467        match value {
468            PValue::U8(u) => Ok(Self::from_bits(u as u64)),
469            PValue::U16(u) => Ok(Self::from_bits(u as u64)),
470            PValue::U32(u) => Ok(Self::from_bits(u as u64)),
471            PValue::U64(u) => Ok(Self::from_bits(u)),
472            PValue::F16(f) => {
473                <Self as NumCast>::from(f).ok_or_else(|| vortex_err!("Cannot convert f16 to f64"))
474            }
475            PValue::F32(f) => {
476                <Self as NumCast>::from(f).ok_or_else(|| vortex_err!("Cannot convert f32 to f64"))
477            }
478            PValue::F64(f) => Ok(f),
479            _ => vortex_bail!("Unsupported PValue {value:?} type for f64"),
480        }
481    }
482}
483
484#[cfg(test)]
485#[allow(clippy::disallowed_types)]
486mod test {
487    use std::cmp::Ordering;
488    use std::collections::HashSet;
489
490    use vortex_dtype::PType;
491    use vortex_dtype::half::f16;
492
493    use crate::PValue;
494
495    #[test]
496    pub fn test_is_instance_of() {
497        assert!(PValue::U8(10).is_instance_of(&PType::U8));
498        assert!(!PValue::U8(10).is_instance_of(&PType::U16));
499        assert!(!PValue::U8(10).is_instance_of(&PType::I8));
500        assert!(!PValue::U8(10).is_instance_of(&PType::F16));
501
502        assert!(PValue::I8(10).is_instance_of(&PType::I8));
503        assert!(!PValue::I8(10).is_instance_of(&PType::I16));
504        assert!(!PValue::I8(10).is_instance_of(&PType::U8));
505        assert!(!PValue::I8(10).is_instance_of(&PType::F16));
506
507        assert!(PValue::F16(f16::from_f32(10.0)).is_instance_of(&PType::F16));
508        assert!(!PValue::F16(f16::from_f32(10.0)).is_instance_of(&PType::F32));
509        assert!(!PValue::F16(f16::from_f32(10.0)).is_instance_of(&PType::U16));
510        assert!(!PValue::F16(f16::from_f32(10.0)).is_instance_of(&PType::I16));
511    }
512
513    #[test]
514    fn test_compare_different_types() {
515        assert_eq!(
516            PValue::I8(4).partial_cmp(&PValue::I8(5)),
517            Some(Ordering::Less)
518        );
519        assert_eq!(
520            PValue::I8(4).partial_cmp(&PValue::I64(5)),
521            Some(Ordering::Less)
522        );
523    }
524
525    #[test]
526    fn test_hash() {
527        let set = HashSet::from([
528            PValue::U8(1),
529            PValue::U16(1),
530            PValue::U32(1),
531            PValue::U64(1),
532            PValue::I8(1),
533            PValue::I16(1),
534            PValue::I32(1),
535            PValue::I64(1),
536            PValue::I8(-1),
537            PValue::I16(-1),
538            PValue::I32(-1),
539            PValue::I64(-1),
540        ]);
541        assert_eq!(set.len(), 2);
542    }
543
544    #[test]
545    fn test_zero_values() {
546        assert_eq!(PValue::zero(PType::U8), PValue::U8(0));
547        assert_eq!(PValue::zero(PType::U16), PValue::U16(0));
548        assert_eq!(PValue::zero(PType::U32), PValue::U32(0));
549        assert_eq!(PValue::zero(PType::U64), PValue::U64(0));
550        assert_eq!(PValue::zero(PType::I8), PValue::I8(0));
551        assert_eq!(PValue::zero(PType::I16), PValue::I16(0));
552        assert_eq!(PValue::zero(PType::I32), PValue::I32(0));
553        assert_eq!(PValue::zero(PType::I64), PValue::I64(0));
554        assert_eq!(PValue::zero(PType::F16), PValue::F16(f16::from_f32(0.0)));
555        assert_eq!(PValue::zero(PType::F32), PValue::F32(0.0));
556        assert_eq!(PValue::zero(PType::F64), PValue::F64(0.0));
557    }
558
559    #[test]
560    fn test_ptype() {
561        assert_eq!(PValue::U8(10).ptype(), PType::U8);
562        assert_eq!(PValue::U16(10).ptype(), PType::U16);
563        assert_eq!(PValue::U32(10).ptype(), PType::U32);
564        assert_eq!(PValue::U64(10).ptype(), PType::U64);
565        assert_eq!(PValue::I8(10).ptype(), PType::I8);
566        assert_eq!(PValue::I16(10).ptype(), PType::I16);
567        assert_eq!(PValue::I32(10).ptype(), PType::I32);
568        assert_eq!(PValue::I64(10).ptype(), PType::I64);
569        assert_eq!(PValue::F16(f16::from_f32(10.0)).ptype(), PType::F16);
570        assert_eq!(PValue::F32(10.0).ptype(), PType::F32);
571        assert_eq!(PValue::F64(10.0).ptype(), PType::F64);
572    }
573
574    #[test]
575    fn test_reinterpret_cast_same_type() {
576        let value = PValue::U32(42);
577        assert_eq!(value.reinterpret_cast(PType::U32), value);
578    }
579
580    #[test]
581    fn test_reinterpret_cast_u8_i8() {
582        let value = PValue::U8(255);
583        let casted = value.reinterpret_cast(PType::I8);
584        assert_eq!(casted, PValue::I8(-1));
585    }
586
587    #[test]
588    fn test_reinterpret_cast_u16_types() {
589        let value = PValue::U16(12345);
590
591        // U16 -> I16
592        let as_i16 = value.reinterpret_cast(PType::I16);
593        assert_eq!(as_i16, PValue::I16(12345));
594
595        // U16 -> F16
596        let as_f16 = value.reinterpret_cast(PType::F16);
597        assert_eq!(as_f16, PValue::F16(f16::from_bits(12345)));
598    }
599
600    #[test]
601    fn test_reinterpret_cast_u32_types() {
602        let value = PValue::U32(0x3f800000); // 1.0 in float bits
603
604        // U32 -> F32
605        let as_f32 = value.reinterpret_cast(PType::F32);
606        assert_eq!(as_f32, PValue::F32(1.0));
607
608        // U32 -> I32
609        let value2 = PValue::U32(0x80000000);
610        let as_i32 = value2.reinterpret_cast(PType::I32);
611        assert_eq!(as_i32, PValue::I32(i32::MIN));
612    }
613
614    #[test]
615    fn test_reinterpret_cast_f32_to_u32() {
616        let value = PValue::F32(1.0);
617        let as_u32 = value.reinterpret_cast(PType::U32);
618        assert_eq!(as_u32, PValue::U32(0x3f800000));
619    }
620
621    #[test]
622    fn test_reinterpret_cast_f64_to_i64() {
623        let value = PValue::F64(1.0);
624        let as_i64 = value.reinterpret_cast(PType::I64);
625        assert_eq!(as_i64, PValue::I64(0x3ff0000000000000_i64));
626    }
627
628    #[test]
629    #[should_panic(expected = "Cannot reinterpret cast between types of different widths")]
630    fn test_reinterpret_cast_different_widths() {
631        let value = PValue::U8(42);
632        let _ = value.reinterpret_cast(PType::U16);
633    }
634
635    #[test]
636    fn test_as_primitive_conversions() {
637        // Test as_u8
638        assert_eq!(PValue::U8(42).as_u8(), Some(42));
639        assert_eq!(PValue::I8(42).as_u8(), Some(42));
640        assert_eq!(PValue::U16(255).as_u8(), Some(255));
641        assert_eq!(PValue::U16(256).as_u8(), None); // Overflow
642
643        // Test as_i32
644        assert_eq!(PValue::I32(42).as_i32(), Some(42));
645        assert_eq!(PValue::U32(42).as_i32(), Some(42));
646        assert_eq!(PValue::I64(42).as_i32(), Some(42));
647        assert_eq!(PValue::U64(u64::MAX).as_i32(), None); // Overflow
648
649        // Test as_f64
650        assert_eq!(PValue::F64(42.5).as_f64(), Some(42.5));
651        assert_eq!(PValue::F32(42.5).as_f64(), Some(42.5f64));
652        assert_eq!(PValue::I32(42).as_f64(), Some(42.0));
653    }
654
655    #[test]
656    fn test_try_from_pvalue_integers() {
657        // Test u8 conversion
658        assert_eq!(u8::try_from(PValue::U8(42)).unwrap(), 42);
659        assert_eq!(u8::try_from(PValue::I8(42)).unwrap(), 42);
660        assert!(u8::try_from(PValue::I8(-1)).is_err());
661        assert!(u8::try_from(PValue::U16(256)).is_err());
662
663        // Test i32 conversion
664        assert_eq!(i32::try_from(PValue::I32(42)).unwrap(), 42);
665        assert_eq!(i32::try_from(PValue::I16(-100)).unwrap(), -100);
666        assert!(i32::try_from(PValue::U64(u64::MAX)).is_err());
667
668        // Float to int should fail
669        assert!(i32::try_from(PValue::F32(42.5)).is_err());
670    }
671
672    #[test]
673    fn test_try_from_pvalue_floats() {
674        // Test f32 conversion
675        assert_eq!(f32::try_from(PValue::F32(42.5)).unwrap(), 42.5);
676        assert_eq!(f32::try_from(PValue::I32(42)).unwrap(), 42.0);
677        assert_eq!(f32::try_from(PValue::U8(255)).unwrap(), 255.0);
678
679        // Test f64 conversion
680        assert_eq!(f64::try_from(PValue::F64(42.5)).unwrap(), 42.5);
681        assert_eq!(f64::try_from(PValue::F32(42.5)).unwrap(), 42.5f64);
682        assert_eq!(f64::try_from(PValue::I64(-100)).unwrap(), -100.0);
683    }
684
685    #[test]
686    fn test_from_usize() {
687        let value: PValue = 42usize.into();
688        assert_eq!(value, PValue::U64(42));
689
690        let max_value: PValue = usize::MAX.into();
691        assert_eq!(max_value, PValue::U64(usize::MAX as u64));
692    }
693
694    #[test]
695    fn test_equality_cross_types() {
696        // Same numeric value, different types
697        assert_eq!(PValue::U8(42), PValue::U16(42));
698        assert_eq!(PValue::U8(42), PValue::U32(42));
699        assert_eq!(PValue::U8(42), PValue::U64(42));
700        assert_eq!(PValue::I8(42), PValue::I16(42));
701        assert_eq!(PValue::I8(42), PValue::I32(42));
702        assert_eq!(PValue::I8(42), PValue::I64(42));
703
704        // Unsigned vs signed with same value (they compare equal even though different categories)
705        assert_eq!(PValue::U8(42), PValue::I8(42));
706        assert_eq!(PValue::U32(42), PValue::I32(42));
707
708        // Float equality
709        assert_eq!(PValue::F32(42.0), PValue::F32(42.0));
710        assert_eq!(PValue::F64(42.0), PValue::F64(42.0));
711        assert_ne!(PValue::F32(42.0), PValue::F64(42.0)); // Different types
712
713        // Float vs int should not be equal
714        assert_ne!(PValue::F32(42.0), PValue::I32(42));
715    }
716
717    #[test]
718    fn test_partial_ord_cross_types() {
719        // Unsigned comparisons
720        assert_eq!(
721            PValue::U8(10).partial_cmp(&PValue::U16(20)),
722            Some(Ordering::Less)
723        );
724        assert_eq!(
725            PValue::U32(30).partial_cmp(&PValue::U8(20)),
726            Some(Ordering::Greater)
727        );
728
729        // Signed comparisons
730        assert_eq!(
731            PValue::I8(-10).partial_cmp(&PValue::I64(0)),
732            Some(Ordering::Less)
733        );
734        assert_eq!(
735            PValue::I32(10).partial_cmp(&PValue::I16(10)),
736            Some(Ordering::Equal)
737        );
738
739        // Float comparisons (same type only)
740        assert_eq!(
741            PValue::F32(1.0).partial_cmp(&PValue::F32(2.0)),
742            Some(Ordering::Less)
743        );
744        assert_eq!(
745            PValue::F64(2.0).partial_cmp(&PValue::F64(1.0)),
746            Some(Ordering::Greater)
747        );
748
749        // Cross-category comparisons - unsigned vs signed work, float vs int don't
750        assert_eq!(
751            PValue::U32(42).partial_cmp(&PValue::I32(42)),
752            Some(Ordering::Equal)
753        ); // Actually works
754        assert_eq!(PValue::F32(42.0).partial_cmp(&PValue::I32(42)), None);
755        assert_eq!(PValue::F32(42.0).partial_cmp(&PValue::F64(42.0)), None);
756    }
757
758    #[test]
759    fn test_to_le_bytes() {
760        use vortex_dtype::ToBytes;
761
762        assert_eq!(PValue::U8(0x12).to_le_bytes(), &[0x12]);
763        assert_eq!(PValue::U16(0x1234).to_le_bytes(), &[0x34, 0x12]);
764        assert_eq!(
765            PValue::U32(0x12345678).to_le_bytes(),
766            &[0x78, 0x56, 0x34, 0x12]
767        );
768
769        assert_eq!(PValue::I8(-1).to_le_bytes(), &[0xFF]);
770        assert_eq!(PValue::I16(-1).to_le_bytes(), &[0xFF, 0xFF]);
771
772        let f32_bytes = PValue::F32(1.0).to_le_bytes();
773        assert_eq!(f32_bytes.len(), 4);
774
775        let f64_bytes = PValue::F64(1.0).to_le_bytes();
776        assert_eq!(f64_bytes.len(), 8);
777    }
778
779    #[test]
780    fn test_f16_special_values() {
781        // Test F16 NaN handling
782        let nan = f16::NAN;
783        let nan_value = PValue::F16(nan);
784        assert!(nan_value.as_f16().unwrap().is_nan());
785
786        // Test F16 infinity
787        let inf = f16::INFINITY;
788        let inf_value = PValue::F16(inf);
789        assert!(inf_value.as_f16().unwrap().is_infinite());
790
791        // Test F16 comparison with NaN
792        assert_eq!(
793            PValue::F16(nan).partial_cmp(&PValue::F16(nan)),
794            Some(Ordering::Equal)
795        );
796    }
797
798    #[test]
799    fn test_coerce_pvalue() {
800        use super::CoercePValue;
801
802        // Test integer coercion
803        assert_eq!(u32::coerce(PValue::U16(42)).unwrap(), 42u32);
804        assert_eq!(i64::coerce(PValue::I32(-42)).unwrap(), -42i64);
805
806        // Test float coercion from bits
807        assert_eq!(f32::coerce(PValue::U32(0x3f800000)).unwrap(), 1.0f32);
808        assert_eq!(
809            f64::coerce(PValue::U64(0x3ff0000000000000)).unwrap(),
810            1.0f64
811        );
812    }
813}