Skip to main content

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    const 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    #[cfg(feature = "alloc")]
108    #[must_use]
109    pub fn from_f64(v: f64) -> Self {
110        unsafe {
111            let ptr = Self::alloc(NumberType::F64);
112            (*ptr).data.f = v;
113            VNumber(Value::new_ptr(ptr.cast(), TypeTag::Number))
114        }
115    }
116
117    /// Returns the number zero.
118    #[cfg(feature = "alloc")]
119    #[must_use]
120    pub fn zero() -> Self {
121        Self::from_i64(0)
122    }
123
124    /// Returns the number one.
125    #[cfg(feature = "alloc")]
126    #[must_use]
127    pub fn one() -> Self {
128        Self::from_i64(1)
129    }
130
131    /// Converts to i64 if it can be represented exactly.
132    #[must_use]
133    pub fn to_i64(&self) -> Option<i64> {
134        let hd = self.header();
135        unsafe {
136            match hd.type_ {
137                NumberType::I64 => Some(hd.data.i),
138                NumberType::U64 => i64::try_from(hd.data.u).ok(),
139                NumberType::F64 => {
140                    let f = hd.data.f;
141                    // Check if in range and is a whole number via round-trip cast
142                    if f >= i64::MIN as f64 && f <= i64::MAX as f64 {
143                        let i = f as i64;
144                        if i as f64 == f {
145                            return Some(i);
146                        }
147                    }
148                    None
149                }
150            }
151        }
152    }
153
154    /// Converts to u64 if it can be represented exactly.
155    #[must_use]
156    pub fn to_u64(&self) -> Option<u64> {
157        let hd = self.header();
158        unsafe {
159            match hd.type_ {
160                NumberType::I64 => u64::try_from(hd.data.i).ok(),
161                NumberType::U64 => Some(hd.data.u),
162                NumberType::F64 => {
163                    let f = hd.data.f;
164                    // Check if in range and is a whole number via round-trip cast
165                    if f >= 0.0 && f <= u64::MAX as f64 {
166                        let u = f as u64;
167                        if u as f64 == f {
168                            return Some(u);
169                        }
170                    }
171                    None
172                }
173            }
174        }
175    }
176
177    /// Converts to f64 if it can be represented exactly.
178    #[must_use]
179    pub fn to_f64(&self) -> Option<f64> {
180        let hd = self.header();
181        unsafe {
182            match hd.type_ {
183                NumberType::I64 => {
184                    let i = hd.data.i;
185                    let f = i as f64;
186                    if f as i64 == i { Some(f) } else { None }
187                }
188                NumberType::U64 => {
189                    let u = hd.data.u;
190                    let f = u as f64;
191                    if f as u64 == u { Some(f) } else { None }
192                }
193                NumberType::F64 => Some(hd.data.f),
194            }
195        }
196    }
197
198    /// Converts to f64, potentially losing precision.
199    #[must_use]
200    pub fn to_f64_lossy(&self) -> f64 {
201        let hd = self.header();
202        unsafe {
203            match hd.type_ {
204                NumberType::I64 => hd.data.i as f64,
205                NumberType::U64 => hd.data.u as f64,
206                NumberType::F64 => hd.data.f,
207            }
208        }
209    }
210
211    /// Converts to i32 if it can be represented exactly.
212    #[must_use]
213    pub fn to_i32(&self) -> Option<i32> {
214        self.to_i64().and_then(|v| i32::try_from(v).ok())
215    }
216
217    /// Converts to u32 if it can be represented exactly.
218    #[must_use]
219    pub fn to_u32(&self) -> Option<u32> {
220        self.to_u64().and_then(|v| u32::try_from(v).ok())
221    }
222
223    /// Converts to f32 if it can be represented exactly.
224    #[must_use]
225    pub fn to_f32(&self) -> Option<f32> {
226        self.to_f64().and_then(|f| {
227            let f32_val = f as f32;
228            if f32_val as f64 == f {
229                Some(f32_val)
230            } else {
231                None
232            }
233        })
234    }
235
236    /// Returns true if this number was created from a floating point value.
237    #[must_use]
238    pub fn is_float(&self) -> bool {
239        self.header().type_ == NumberType::F64
240    }
241
242    /// Returns true if this number is an integer (signed or unsigned).
243    #[must_use]
244    pub fn is_integer(&self) -> bool {
245        matches!(self.header().type_, NumberType::I64 | NumberType::U64)
246    }
247
248    pub(crate) fn clone_impl(&self) -> Value {
249        let hd = self.header();
250        unsafe {
251            match hd.type_ {
252                NumberType::I64 => Self::from_i64(hd.data.i).0,
253                NumberType::U64 => {
254                    let ptr = Self::alloc(NumberType::U64);
255                    (*ptr).data.u = hd.data.u;
256                    Value::new_ptr(ptr.cast(), TypeTag::Number)
257                }
258                NumberType::F64 => Self::from_f64(hd.data.f).0,
259            }
260        }
261    }
262
263    pub(crate) fn drop_impl(&mut self) {
264        unsafe {
265            Self::dealloc(self.0.heap_ptr_mut().cast());
266        }
267    }
268}
269
270impl PartialEq for VNumber {
271    fn eq(&self, other: &Self) -> bool {
272        self.partial_cmp(other) == Some(Ordering::Equal)
273    }
274}
275
276impl PartialOrd for VNumber {
277    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
278        let h1 = self.header();
279        let h2 = other.header();
280
281        unsafe {
282            // Fast path: same type
283            if h1.type_ == h2.type_ {
284                match h1.type_ {
285                    NumberType::I64 => Some(h1.data.i.cmp(&h2.data.i)),
286                    NumberType::U64 => Some(h1.data.u.cmp(&h2.data.u)),
287                    NumberType::F64 => h1.data.f.partial_cmp(&h2.data.f),
288                }
289            } else {
290                // Cross-type comparison: convert to f64 for simplicity
291                // (This loses precision for very large integers, but is simple)
292                self.to_f64_lossy().partial_cmp(&other.to_f64_lossy())
293            }
294        }
295    }
296}
297
298impl Hash for VNumber {
299    fn hash<H: Hasher>(&self, state: &mut H) {
300        // Hash based on the "canonical" representation
301        if let Some(i) = self.to_i64() {
302            0u8.hash(state); // discriminant for integer
303            i.hash(state);
304        } else if let Some(u) = self.to_u64() {
305            1u8.hash(state); // discriminant for large unsigned
306            u.hash(state);
307        } else if let Some(f) = self.to_f64() {
308            2u8.hash(state); // discriminant for float
309            f.to_bits().hash(state);
310        }
311    }
312}
313
314impl Debug for VNumber {
315    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
316        if let Some(i) = self.to_i64() {
317            Debug::fmt(&i, f)
318        } else if let Some(u) = self.to_u64() {
319            Debug::fmt(&u, f)
320        } else if let Some(fl) = self.to_f64() {
321            Debug::fmt(&fl, f)
322        } else {
323            f.write_str("NaN")
324        }
325    }
326}
327
328impl Default for VNumber {
329    fn default() -> Self {
330        Self::zero()
331    }
332}
333
334// === From implementations ===
335
336macro_rules! impl_from_int {
337    ($($t:ty => $method:ident),* $(,)?) => {
338        $(
339            #[cfg(feature = "alloc")]
340            impl From<$t> for VNumber {
341                fn from(v: $t) -> Self {
342                    Self::$method(v as _)
343                }
344            }
345
346            #[cfg(feature = "alloc")]
347            impl From<$t> for Value {
348                fn from(v: $t) -> Self {
349                    VNumber::from(v).0
350                }
351            }
352        )*
353    };
354}
355
356impl_from_int! {
357    i8 => from_i64,
358    i16 => from_i64,
359    i32 => from_i64,
360    i64 => from_i64,
361    isize => from_i64,
362    u8 => from_i64,
363    u16 => from_i64,
364    u32 => from_i64,
365    u64 => from_u64,
366    usize => from_u64,
367}
368
369#[cfg(feature = "alloc")]
370impl From<f32> for VNumber {
371    fn from(v: f32) -> Self {
372        Self::from_f64(f64::from(v))
373    }
374}
375
376#[cfg(feature = "alloc")]
377impl From<f64> for VNumber {
378    fn from(v: f64) -> Self {
379        Self::from_f64(v)
380    }
381}
382
383#[cfg(feature = "alloc")]
384impl From<f32> for Value {
385    fn from(v: f32) -> Self {
386        VNumber::from_f64(f64::from(v)).into_value()
387    }
388}
389
390#[cfg(feature = "alloc")]
391impl From<f64> for Value {
392    fn from(v: f64) -> Self {
393        VNumber::from_f64(v).into_value()
394    }
395}
396
397// === Conversion traits ===
398
399impl AsRef<Value> for VNumber {
400    fn as_ref(&self) -> &Value {
401        &self.0
402    }
403}
404
405impl AsMut<Value> for VNumber {
406    fn as_mut(&mut self) -> &mut Value {
407        &mut self.0
408    }
409}
410
411impl From<VNumber> for Value {
412    fn from(n: VNumber) -> Self {
413        n.0
414    }
415}
416
417impl VNumber {
418    /// Converts this VNumber into a Value, consuming self.
419    #[inline]
420    pub fn into_value(self) -> Value {
421        self.0
422    }
423}
424
425#[cfg(test)]
426mod tests {
427    use super::*;
428
429    #[test]
430    fn test_i64() {
431        let n = VNumber::from_i64(42);
432        assert_eq!(n.to_i64(), Some(42));
433        assert_eq!(n.to_u64(), Some(42));
434        assert_eq!(n.to_f64(), Some(42.0));
435        assert!(n.is_integer());
436        assert!(!n.is_float());
437    }
438
439    #[test]
440    fn test_negative() {
441        let n = VNumber::from_i64(-100);
442        assert_eq!(n.to_i64(), Some(-100));
443        assert_eq!(n.to_u64(), None);
444        assert_eq!(n.to_f64(), Some(-100.0));
445    }
446
447    #[test]
448    fn test_large_u64() {
449        let v = u64::MAX;
450        let n = VNumber::from_u64(v);
451        assert_eq!(n.to_u64(), Some(v));
452        assert_eq!(n.to_i64(), None);
453    }
454
455    #[test]
456    fn test_f64() {
457        let n = VNumber::from_f64(2.5);
458        assert_eq!(n.to_f64(), Some(2.5));
459        assert_eq!(n.to_i64(), None); // has fractional part
460        assert!(n.is_float());
461        assert!(!n.is_integer());
462    }
463
464    #[test]
465    fn test_f64_whole() {
466        let n = VNumber::from_f64(42.0);
467        assert_eq!(n.to_f64(), Some(42.0));
468        assert_eq!(n.to_i64(), Some(42)); // whole number
469    }
470
471    #[test]
472    fn test_nan_roundtrip() {
473        assert!(VNumber::from_f64(f64::NAN).to_f64().unwrap().is_nan());
474        assert_eq!(
475            VNumber::from_f64(f64::INFINITY).to_f64().unwrap(),
476            f64::INFINITY
477        );
478        assert_eq!(
479            VNumber::from_f64(f64::NEG_INFINITY).to_f64().unwrap(),
480            f64::NEG_INFINITY
481        );
482    }
483
484    #[test]
485    fn test_equality() {
486        let a = VNumber::from_i64(42);
487        let b = VNumber::from_i64(42);
488        let c = VNumber::from_f64(42.0);
489        let nan = VNumber::from_f64(f64::NAN);
490
491        assert_eq!(a, b);
492        assert_eq!(a, c); // integer 42 equals float 42.0
493
494        // nan should != any value including itself
495        assert_ne!(c, nan);
496        assert_ne!(nan, nan);
497    }
498
499    #[test]
500    fn test_ordering() {
501        let a = VNumber::from_i64(1);
502        let b = VNumber::from_i64(2);
503        let c = VNumber::from_f64(1.5);
504        let nan = VNumber::from_f64(f64::NAN);
505        let inf = VNumber::from_f64(f64::INFINITY);
506
507        assert!(a < b);
508        assert!(a < c);
509        assert!(c < b);
510        assert!(b < inf);
511        assert!(!(c > nan || c < nan));
512    }
513}