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