facet_value/
number.rs

1//! Number value type with efficient storage for various numeric types.
2
3#[cfg(feature = "alloc")]
4use alloc::alloc::{Layout, alloc, dealloc};
5use core::cmp::Ordering;
6use core::fmt::{self, Debug, Formatter};
7use core::hash::{Hash, Hasher};
8
9use crate::value::{TypeTag, Value};
10
11/// Internal representation of number type.
12#[repr(u8)]
13#[derive(Copy, Clone, Debug, PartialEq, Eq)]
14enum NumberType {
15    /// Signed 64-bit integer
16    I64 = 0,
17    /// Unsigned 64-bit integer
18    U64 = 1,
19    /// 64-bit floating point
20    F64 = 2,
21}
22
23/// Header for heap-allocated numbers.
24#[repr(C, align(8))]
25struct NumberHeader {
26    /// Type discriminant
27    type_: NumberType,
28    /// Padding
29    _pad: [u8; 7],
30    /// The actual number data (i64, u64, or f64)
31    data: NumberData,
32}
33
34#[repr(C)]
35union NumberData {
36    i: i64,
37    u: u64,
38    f: f64,
39}
40
41/// A JSON number value.
42///
43/// `VNumber` can represent integers (signed and unsigned) and floating point numbers.
44/// It stores the number in the most appropriate internal format.
45#[repr(transparent)]
46#[derive(Clone)]
47pub struct VNumber(pub(crate) Value);
48
49impl VNumber {
50    fn layout() -> Layout {
51        Layout::new::<NumberHeader>()
52    }
53
54    #[cfg(feature = "alloc")]
55    fn alloc(type_: NumberType) -> *mut NumberHeader {
56        unsafe {
57            let ptr = alloc(Self::layout()).cast::<NumberHeader>();
58            (*ptr).type_ = type_;
59            ptr
60        }
61    }
62
63    #[cfg(feature = "alloc")]
64    fn dealloc(ptr: *mut NumberHeader) {
65        unsafe {
66            dealloc(ptr.cast::<u8>(), Self::layout());
67        }
68    }
69
70    fn header(&self) -> &NumberHeader {
71        unsafe { &*(self.0.heap_ptr() as *const NumberHeader) }
72    }
73
74    #[allow(dead_code)]
75    fn header_mut(&mut self) -> &mut NumberHeader {
76        unsafe { &mut *(self.0.heap_ptr_mut() as *mut NumberHeader) }
77    }
78
79    /// Creates a number from an i64.
80    #[cfg(feature = "alloc")]
81    #[must_use]
82    pub fn from_i64(v: i64) -> Self {
83        unsafe {
84            let ptr = Self::alloc(NumberType::I64);
85            (*ptr).data.i = v;
86            VNumber(Value::new_ptr(ptr.cast(), TypeTag::Number))
87        }
88    }
89
90    /// Creates a number from a u64.
91    #[cfg(feature = "alloc")]
92    #[must_use]
93    pub fn from_u64(v: u64) -> Self {
94        // If it fits in i64, use that for consistency
95        if let Ok(i) = i64::try_from(v) {
96            Self::from_i64(i)
97        } else {
98            unsafe {
99                let ptr = Self::alloc(NumberType::U64);
100                (*ptr).data.u = v;
101                VNumber(Value::new_ptr(ptr.cast(), TypeTag::Number))
102            }
103        }
104    }
105
106    /// Creates a number from an f64.
107    ///
108    /// Returns `None` if the value is NaN or infinite.
109    #[cfg(feature = "alloc")]
110    #[must_use]
111    pub fn from_f64(v: f64) -> Option<Self> {
112        if !v.is_finite() {
113            return None;
114        }
115        unsafe {
116            let ptr = Self::alloc(NumberType::F64);
117            (*ptr).data.f = v;
118            Some(VNumber(Value::new_ptr(ptr.cast(), TypeTag::Number)))
119        }
120    }
121
122    /// Returns the number zero.
123    #[cfg(feature = "alloc")]
124    #[must_use]
125    pub fn zero() -> Self {
126        Self::from_i64(0)
127    }
128
129    /// Returns the number one.
130    #[cfg(feature = "alloc")]
131    #[must_use]
132    pub fn one() -> Self {
133        Self::from_i64(1)
134    }
135
136    /// Converts to i64 if it can be represented exactly.
137    #[must_use]
138    pub fn to_i64(&self) -> Option<i64> {
139        let hd = self.header();
140        unsafe {
141            match hd.type_ {
142                NumberType::I64 => Some(hd.data.i),
143                NumberType::U64 => i64::try_from(hd.data.u).ok(),
144                NumberType::F64 => {
145                    let f = hd.data.f;
146                    // Check if in range and is a whole number via round-trip cast
147                    if f >= i64::MIN as f64 && f <= i64::MAX as f64 {
148                        let i = f as i64;
149                        if i as f64 == f {
150                            return Some(i);
151                        }
152                    }
153                    None
154                }
155            }
156        }
157    }
158
159    /// Converts to u64 if it can be represented exactly.
160    #[must_use]
161    pub fn to_u64(&self) -> Option<u64> {
162        let hd = self.header();
163        unsafe {
164            match hd.type_ {
165                NumberType::I64 => u64::try_from(hd.data.i).ok(),
166                NumberType::U64 => Some(hd.data.u),
167                NumberType::F64 => {
168                    let f = hd.data.f;
169                    // Check if in range and is a whole number via round-trip cast
170                    if f >= 0.0 && f <= u64::MAX as f64 {
171                        let u = f as u64;
172                        if u as f64 == f {
173                            return Some(u);
174                        }
175                    }
176                    None
177                }
178            }
179        }
180    }
181
182    /// Converts to f64 if it can be represented exactly.
183    #[must_use]
184    pub fn to_f64(&self) -> Option<f64> {
185        let hd = self.header();
186        unsafe {
187            match hd.type_ {
188                NumberType::I64 => {
189                    let i = hd.data.i;
190                    let f = i as f64;
191                    if f as i64 == i { Some(f) } else { None }
192                }
193                NumberType::U64 => {
194                    let u = hd.data.u;
195                    let f = u as f64;
196                    if f as u64 == u { Some(f) } else { None }
197                }
198                NumberType::F64 => Some(hd.data.f),
199            }
200        }
201    }
202
203    /// Converts to f64, potentially losing precision.
204    #[must_use]
205    pub fn to_f64_lossy(&self) -> f64 {
206        let hd = self.header();
207        unsafe {
208            match hd.type_ {
209                NumberType::I64 => hd.data.i as f64,
210                NumberType::U64 => hd.data.u as f64,
211                NumberType::F64 => hd.data.f,
212            }
213        }
214    }
215
216    /// Converts to i32 if it can be represented exactly.
217    #[must_use]
218    pub fn to_i32(&self) -> Option<i32> {
219        self.to_i64().and_then(|v| i32::try_from(v).ok())
220    }
221
222    /// Converts to u32 if it can be represented exactly.
223    #[must_use]
224    pub fn to_u32(&self) -> Option<u32> {
225        self.to_u64().and_then(|v| u32::try_from(v).ok())
226    }
227
228    /// Converts to f32 if it can be represented exactly.
229    #[must_use]
230    pub fn to_f32(&self) -> Option<f32> {
231        self.to_f64().and_then(|f| {
232            let f32_val = f as f32;
233            if f32_val as f64 == f {
234                Some(f32_val)
235            } else {
236                None
237            }
238        })
239    }
240
241    /// Returns true if this number was created from a floating point value.
242    #[must_use]
243    pub fn is_float(&self) -> bool {
244        self.header().type_ == NumberType::F64
245    }
246
247    /// Returns true if this number is an integer (signed or unsigned).
248    #[must_use]
249    pub fn is_integer(&self) -> bool {
250        matches!(self.header().type_, NumberType::I64 | NumberType::U64)
251    }
252
253    pub(crate) fn clone_impl(&self) -> Value {
254        let hd = self.header();
255        unsafe {
256            match hd.type_ {
257                NumberType::I64 => Self::from_i64(hd.data.i).0,
258                NumberType::U64 => {
259                    let ptr = Self::alloc(NumberType::U64);
260                    (*ptr).data.u = hd.data.u;
261                    Value::new_ptr(ptr.cast(), TypeTag::Number)
262                }
263                NumberType::F64 => Self::from_f64(hd.data.f).unwrap().0,
264            }
265        }
266    }
267
268    pub(crate) fn drop_impl(&mut self) {
269        unsafe {
270            Self::dealloc(self.0.heap_ptr_mut().cast());
271        }
272    }
273}
274
275impl PartialEq for VNumber {
276    fn eq(&self, other: &Self) -> bool {
277        self.cmp(other) == Ordering::Equal
278    }
279}
280
281impl Eq for VNumber {}
282
283impl PartialOrd for VNumber {
284    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
285        Some(self.cmp(other))
286    }
287}
288
289impl Ord for VNumber {
290    fn cmp(&self, other: &Self) -> Ordering {
291        let h1 = self.header();
292        let h2 = other.header();
293
294        unsafe {
295            // Fast path: same type
296            if h1.type_ == h2.type_ {
297                match h1.type_ {
298                    NumberType::I64 => h1.data.i.cmp(&h2.data.i),
299                    NumberType::U64 => h1.data.u.cmp(&h2.data.u),
300                    NumberType::F64 => h1.data.f.partial_cmp(&h2.data.f).unwrap_or(Ordering::Equal),
301                }
302            } else {
303                // Cross-type comparison: convert to f64 for simplicity
304                // (This loses precision for very large integers, but is simple)
305                self.to_f64_lossy()
306                    .partial_cmp(&other.to_f64_lossy())
307                    .unwrap_or(Ordering::Equal)
308            }
309        }
310    }
311}
312
313impl Hash for VNumber {
314    fn hash<H: Hasher>(&self, state: &mut H) {
315        // Hash based on the "canonical" representation
316        if let Some(i) = self.to_i64() {
317            0u8.hash(state); // discriminant for integer
318            i.hash(state);
319        } else if let Some(u) = self.to_u64() {
320            1u8.hash(state); // discriminant for large unsigned
321            u.hash(state);
322        } else if let Some(f) = self.to_f64() {
323            2u8.hash(state); // discriminant for float
324            f.to_bits().hash(state);
325        }
326    }
327}
328
329impl Debug for VNumber {
330    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
331        if let Some(i) = self.to_i64() {
332            Debug::fmt(&i, f)
333        } else if let Some(u) = self.to_u64() {
334            Debug::fmt(&u, f)
335        } else if let Some(fl) = self.to_f64() {
336            Debug::fmt(&fl, f)
337        } else {
338            f.write_str("NaN")
339        }
340    }
341}
342
343impl Default for VNumber {
344    fn default() -> Self {
345        Self::zero()
346    }
347}
348
349// === From implementations ===
350
351macro_rules! impl_from_int {
352    ($($t:ty => $method:ident),* $(,)?) => {
353        $(
354            #[cfg(feature = "alloc")]
355            impl From<$t> for VNumber {
356                fn from(v: $t) -> Self {
357                    Self::$method(v as _)
358                }
359            }
360
361            #[cfg(feature = "alloc")]
362            impl From<$t> for Value {
363                fn from(v: $t) -> Self {
364                    VNumber::from(v).0
365                }
366            }
367        )*
368    };
369}
370
371impl_from_int! {
372    i8 => from_i64,
373    i16 => from_i64,
374    i32 => from_i64,
375    i64 => from_i64,
376    isize => from_i64,
377    u8 => from_i64,
378    u16 => from_i64,
379    u32 => from_i64,
380    u64 => from_u64,
381    usize => from_u64,
382}
383
384#[cfg(feature = "alloc")]
385impl TryFrom<f32> for VNumber {
386    type Error = ();
387
388    fn try_from(v: f32) -> Result<Self, Self::Error> {
389        Self::from_f64(f64::from(v)).ok_or(())
390    }
391}
392
393#[cfg(feature = "alloc")]
394impl TryFrom<f64> for VNumber {
395    type Error = ();
396
397    fn try_from(v: f64) -> Result<Self, Self::Error> {
398        Self::from_f64(v).ok_or(())
399    }
400}
401
402#[cfg(feature = "alloc")]
403impl From<f32> for Value {
404    fn from(v: f32) -> Self {
405        VNumber::from_f64(f64::from(v))
406            .map(|n| n.0)
407            .unwrap_or(Value::NULL)
408    }
409}
410
411#[cfg(feature = "alloc")]
412impl From<f64> for Value {
413    fn from(v: f64) -> Self {
414        VNumber::from_f64(v).map(|n| n.0).unwrap_or(Value::NULL)
415    }
416}
417
418// === Conversion traits ===
419
420impl AsRef<Value> for VNumber {
421    fn as_ref(&self) -> &Value {
422        &self.0
423    }
424}
425
426impl AsMut<Value> for VNumber {
427    fn as_mut(&mut self) -> &mut Value {
428        &mut self.0
429    }
430}
431
432impl From<VNumber> for Value {
433    fn from(n: VNumber) -> Self {
434        n.0
435    }
436}
437
438impl VNumber {
439    /// Converts this VNumber into a Value, consuming self.
440    #[inline]
441    pub fn into_value(self) -> Value {
442        self.0
443    }
444}
445
446#[cfg(test)]
447mod tests {
448    use super::*;
449
450    #[test]
451    fn test_i64() {
452        let n = VNumber::from_i64(42);
453        assert_eq!(n.to_i64(), Some(42));
454        assert_eq!(n.to_u64(), Some(42));
455        assert_eq!(n.to_f64(), Some(42.0));
456        assert!(n.is_integer());
457        assert!(!n.is_float());
458    }
459
460    #[test]
461    fn test_negative() {
462        let n = VNumber::from_i64(-100);
463        assert_eq!(n.to_i64(), Some(-100));
464        assert_eq!(n.to_u64(), None);
465        assert_eq!(n.to_f64(), Some(-100.0));
466    }
467
468    #[test]
469    fn test_large_u64() {
470        let v = u64::MAX;
471        let n = VNumber::from_u64(v);
472        assert_eq!(n.to_u64(), Some(v));
473        assert_eq!(n.to_i64(), None);
474    }
475
476    #[test]
477    fn test_f64() {
478        let n = VNumber::from_f64(2.5).unwrap();
479        assert_eq!(n.to_f64(), Some(2.5));
480        assert_eq!(n.to_i64(), None); // has fractional part
481        assert!(n.is_float());
482        assert!(!n.is_integer());
483    }
484
485    #[test]
486    fn test_f64_whole() {
487        let n = VNumber::from_f64(42.0).unwrap();
488        assert_eq!(n.to_f64(), Some(42.0));
489        assert_eq!(n.to_i64(), Some(42)); // whole number
490    }
491
492    #[test]
493    fn test_nan_rejected() {
494        assert!(VNumber::from_f64(f64::NAN).is_none());
495        assert!(VNumber::from_f64(f64::INFINITY).is_none());
496        assert!(VNumber::from_f64(f64::NEG_INFINITY).is_none());
497    }
498
499    #[test]
500    fn test_equality() {
501        let a = VNumber::from_i64(42);
502        let b = VNumber::from_i64(42);
503        let c = VNumber::from_f64(42.0).unwrap();
504
505        assert_eq!(a, b);
506        assert_eq!(a, c); // integer 42 equals float 42.0
507    }
508
509    #[test]
510    fn test_ordering() {
511        let a = VNumber::from_i64(1);
512        let b = VNumber::from_i64(2);
513        let c = VNumber::from_f64(1.5).unwrap();
514
515        assert!(a < b);
516        assert!(a < c);
517        assert!(c < b);
518    }
519}