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