datalogic_rs/value/
number.rs

1//! Efficient numeric value representation.
2//!
3//! This module provides a specialized representation for numeric values
4//! to optimize memory usage based on the actual value.
5
6use std::cmp::Ordering;
7use std::fmt;
8
9/// Specialized representation for numeric values to optimize memory usage.
10///
11/// This enum provides different representations for integers and floating-point
12/// values, allowing for more efficient memory usage and operations.
13#[derive(Debug, Clone, Copy)]
14pub enum NumberValue {
15    /// Integer value
16    Integer(i64),
17
18    /// Floating point value
19    Float(f64),
20}
21
22impl NumberValue {
23    /// Creates a new NumberValue from an i64.
24    pub fn from_i64(value: i64) -> Self {
25        NumberValue::Integer(value)
26    }
27
28    /// Creates a new NumberValue from an f64.
29    pub fn from_f64(value: f64) -> Self {
30        // Store integers as integers when possible
31        if value.fract() == 0.0 && value >= i64::MIN as f64 && value <= i64::MAX as f64 {
32            NumberValue::Integer(value as i64)
33        } else {
34            NumberValue::Float(value)
35        }
36    }
37
38    /// Returns true if the value is an integer.
39    pub fn is_integer(&self) -> bool {
40        matches!(self, NumberValue::Integer(_))
41    }
42
43    /// Returns true if the value is a floating point.
44    pub fn is_float(&self) -> bool {
45        matches!(self, NumberValue::Float(_))
46    }
47
48    /// Returns the value as an i64, if possible.
49    pub fn as_i64(&self) -> Option<i64> {
50        match *self {
51            NumberValue::Integer(i) => Some(i),
52            NumberValue::Float(f) => {
53                if f.fract() == 0.0 && f >= i64::MIN as f64 && f <= i64::MAX as f64 {
54                    Some(f as i64)
55                } else {
56                    None
57                }
58            }
59        }
60    }
61
62    /// Returns the value as an f64.
63    pub fn as_f64(&self) -> f64 {
64        match *self {
65            NumberValue::Integer(i) => i as f64,
66            NumberValue::Float(f) => f,
67        }
68    }
69
70    /// Adds another NumberValue to this one.
71    pub fn add(&self, other: &NumberValue) -> NumberValue {
72        match (*self, *other) {
73            (NumberValue::Integer(a), NumberValue::Integer(b)) => {
74                // Check for overflow
75                match a.checked_add(b) {
76                    Some(result) => NumberValue::Integer(result),
77                    None => NumberValue::Float(a as f64 + b as f64),
78                }
79            }
80            _ => NumberValue::from_f64(self.as_f64() + other.as_f64()),
81        }
82    }
83
84    /// Subtracts another NumberValue from this one.
85    pub fn subtract(&self, other: &NumberValue) -> NumberValue {
86        match (*self, *other) {
87            (NumberValue::Integer(a), NumberValue::Integer(b)) => {
88                // Check for overflow
89                match a.checked_sub(b) {
90                    Some(result) => NumberValue::Integer(result),
91                    None => NumberValue::Float(a as f64 - b as f64),
92                }
93            }
94            _ => NumberValue::from_f64(self.as_f64() - other.as_f64()),
95        }
96    }
97
98    /// Multiplies this NumberValue by another.
99    pub fn multiply(&self, other: &NumberValue) -> NumberValue {
100        match (*self, *other) {
101            (NumberValue::Integer(a), NumberValue::Integer(b)) => {
102                // Check for overflow
103                match a.checked_mul(b) {
104                    Some(result) => NumberValue::Integer(result),
105                    None => NumberValue::Float(a as f64 * b as f64),
106                }
107            }
108            _ => NumberValue::from_f64(self.as_f64() * other.as_f64()),
109        }
110    }
111
112    /// Divides this NumberValue by another.
113    pub fn divide(&self, other: &NumberValue) -> Option<NumberValue> {
114        let divisor = other.as_f64();
115        if divisor == 0.0 {
116            return None;
117        }
118
119        match (*self, *other) {
120            (NumberValue::Integer(a), NumberValue::Integer(b)) => {
121                if a % b == 0 {
122                    Some(NumberValue::Integer(a / b))
123                } else {
124                    Some(NumberValue::Float(a as f64 / b as f64))
125                }
126            }
127            _ => Some(NumberValue::from_f64(self.as_f64() / divisor)),
128        }
129    }
130
131    /// Returns the modulo of this NumberValue by another.
132    pub fn modulo(&self, other: &NumberValue) -> Option<NumberValue> {
133        let divisor = other.as_f64();
134        if divisor == 0.0 {
135            return None;
136        }
137
138        match (*self, *other) {
139            (NumberValue::Integer(a), NumberValue::Integer(b)) => Some(NumberValue::Integer(a % b)),
140            _ => Some(NumberValue::from_f64(self.as_f64() % divisor)),
141        }
142    }
143}
144
145impl PartialEq for NumberValue {
146    fn eq(&self, other: &Self) -> bool {
147        match (*self, *other) {
148            (NumberValue::Integer(a), NumberValue::Integer(b)) => a == b,
149            (NumberValue::Float(a), NumberValue::Float(b)) => a == b,
150            (NumberValue::Integer(a), NumberValue::Float(b)) => (a as f64) == b,
151            (NumberValue::Float(a), NumberValue::Integer(b)) => a == (b as f64),
152        }
153    }
154}
155
156impl Eq for NumberValue {}
157
158impl PartialOrd for NumberValue {
159    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
160        match (*self, *other) {
161            (NumberValue::Integer(a), NumberValue::Integer(b)) => a.partial_cmp(&b),
162            (NumberValue::Float(a), NumberValue::Float(b)) => a.partial_cmp(&b),
163            (NumberValue::Integer(a), NumberValue::Float(b)) => (a as f64).partial_cmp(&b),
164            (NumberValue::Float(a), NumberValue::Integer(b)) => a.partial_cmp(&(b as f64)),
165        }
166    }
167}
168
169impl fmt::Display for NumberValue {
170    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171        match *self {
172            NumberValue::Integer(i) => write!(f, "{}", i),
173            NumberValue::Float(fl) => write!(f, "{}", fl),
174        }
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181
182    #[test]
183    fn test_number_creation() {
184        let int = NumberValue::from_i64(42);
185        let float = NumberValue::from_f64(3.14);
186        let int_from_float = NumberValue::from_f64(42.0);
187
188        assert!(int.is_integer());
189        assert!(float.is_float());
190        assert!(int_from_float.is_integer());
191
192        assert_eq!(int.as_i64(), Some(42));
193        assert_eq!(float.as_i64(), None);
194        assert_eq!(int_from_float.as_i64(), Some(42));
195
196        assert_eq!(int.as_f64(), 42.0);
197        assert_eq!(float.as_f64(), 3.14);
198    }
199
200    #[test]
201    fn test_number_operations() {
202        let a = NumberValue::from_i64(5);
203        let b = NumberValue::from_i64(3);
204        let c = NumberValue::from_f64(2.5);
205
206        assert_eq!(a.add(&b), NumberValue::from_i64(8));
207        assert_eq!(a.subtract(&b), NumberValue::from_i64(2));
208        assert_eq!(a.multiply(&b), NumberValue::from_i64(15));
209        assert_eq!(a.divide(&b).unwrap(), NumberValue::from_f64(5.0 / 3.0));
210
211        assert_eq!(a.add(&c), NumberValue::from_f64(7.5));
212        assert_eq!(a.subtract(&c), NumberValue::from_f64(2.5));
213        assert_eq!(a.multiply(&c), NumberValue::from_f64(12.5));
214        assert_eq!(a.divide(&c).unwrap(), NumberValue::from_f64(2.0));
215    }
216
217    #[test]
218    fn test_number_comparison() {
219        let a = NumberValue::from_i64(5);
220        let b = NumberValue::from_i64(3);
221        let c = NumberValue::from_f64(5.0);
222        let d = NumberValue::from_f64(3.5);
223
224        assert!(a > b);
225        assert!(a == c);
226        assert!(a > d);
227        assert!(d > b);
228    }
229}