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