raystack_core/
number.rs

1/// A Haystack number.
2#[derive(Clone, Debug, PartialEq)]
3pub enum Number {
4    Basic(BasicNumber),
5    Scientific(ScientificNumber),
6}
7
8impl std::fmt::Display for Number {
9    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
10        match self {
11            Self::Basic(num) => write!(f, "{}", num),
12            Self::Scientific(ex) => write!(f, "{}", ex),
13        }
14    }
15}
16
17impl Number {
18    /// Create a new `Number`. If present, the unit should
19    /// be a valid unit string from Project Haystack's
20    /// unit database.
21    pub fn new(value: f64, unit: Option<String>) -> Self {
22        Number::Basic(BasicNumber::new(value, unit))
23    }
24
25    /// Create a new `Number` and no unit.
26    pub fn new_unitless(value: f64) -> Self {
27        Self::new(value, None)
28    }
29
30    /// Create a new scientific notation `Number`. If present, the unit should
31    /// be a valid unit string from Project Haystack's
32    /// unit database.
33    pub fn new_scientific(
34        significand: f64,
35        exponent: i32,
36        unit: Option<String>,
37    ) -> Option<Self> {
38        Some(Number::Scientific(ScientificNumber::new(
39            significand,
40            exponent,
41            unit,
42        )?))
43    }
44
45    /// Create a new scientific notation `Number` and no unit.
46    pub fn new_scientific_unitless(
47        significand: f64,
48        exponent: i32,
49    ) -> Option<Self> {
50        Self::new_scientific(significand, exponent, None)
51    }
52
53    /// If this represents a non-scientific notation number, return the number.
54    pub fn as_number(&self) -> Option<&BasicNumber> {
55        match self {
56            Self::Basic(number) => Some(number),
57            _ => None,
58        }
59    }
60
61    /// If this represents a number in scientific notation,
62    /// return the scientific notation number.
63    pub fn as_scientific_number(&self) -> Option<&ScientificNumber> {
64        match self {
65            Self::Scientific(ex) => Some(ex),
66            _ => None,
67        }
68    }
69
70    /// Return the unit component of this `Number`, if present.
71    pub fn unit(&self) -> Option<&str> {
72        match self {
73            Self::Basic(num) => num.unit(),
74            Self::Scientific(ex) => ex.unit(),
75        }
76    }
77
78    /// Return a string containing Axon code representing this number.
79    pub fn to_axon_code(&self) -> String {
80        match self {
81            Self::Basic(num) => num.to_axon_code(),
82            Self::Scientific(ex) => ex.to_axon_code(),
83        }
84    }
85}
86
87/// A Haystack Number, encapsulating a scalar value and
88/// an optional unit value. The unit is represented as a
89/// string. This does not represent Haystack scientific notation numbers.
90#[derive(Clone, Debug, PartialEq)]
91pub struct BasicNumber {
92    value: f64,
93    unit: Option<String>,
94}
95
96impl BasicNumber {
97    /// Create a new `BasicNumber`. If present, the unit should
98    /// be a valid unit string from Project Haystack's
99    /// unit database.
100    pub fn new(value: f64, unit: Option<String>) -> Self {
101        Self { value, unit }
102    }
103
104    /// Create a new `BasicNumber` with no unit.
105    pub fn new_unitless(value: f64) -> Self {
106        Self::new(value, None)
107    }
108
109    /// Return the numeric component of this number.
110    pub fn value(&self) -> f64 {
111        self.value
112    }
113
114    /// Return the unit component of this number, if present.
115    pub fn unit(&self) -> Option<&str> {
116        self.unit.as_ref().map(|unit| unit.as_ref())
117    }
118
119    /// Return a string containing Axon code representing this number.
120    pub fn to_axon_code(&self) -> String {
121        let value = self.value();
122        if let Some(unit) = self.unit() {
123            if value.is_nan() {
124                format!("nan().as(\"{}\")", unit)
125            } else if value.is_infinite() && value.is_sign_positive() {
126                format!("posInf().as(\"{}\")", unit)
127            } else if value.is_infinite() && value.is_sign_negative() {
128                format!("negInf().as(\"{}\")", unit)
129            } else {
130                format!("{}{}", value, unit)
131            }
132        } else {
133            if value.is_nan() {
134                "nan()".to_owned()
135            } else if value.is_infinite() && value.is_sign_positive() {
136                "posInf()".to_owned()
137            } else if value.is_infinite() && value.is_sign_negative() {
138                "negInf()".to_owned()
139            } else {
140                format!("{}", value)
141            }
142        }
143    }
144}
145
146impl std::fmt::Display for BasicNumber {
147    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148        let value = self.value();
149        if value.is_nan() {
150            if let Some(unit) = self.unit() {
151                write!(f, "NaN {}", unit)
152            } else {
153                write!(f, "NaN")
154            }
155        } else if value.is_infinite() && value.is_sign_positive() {
156            if let Some(unit) = self.unit() {
157                write!(f, "INF {}", unit)
158            } else {
159                write!(f, "INF")
160            }
161        } else if value.is_infinite() && value.is_sign_negative() {
162            if let Some(unit) = self.unit() {
163                write!(f, "-INF {}", unit)
164            } else {
165                write!(f, "-INF")
166            }
167        } else if let Some(unit) = self.unit() {
168            write!(f, "{} {}", value, unit)
169        } else {
170            write!(f, "{}", value)
171        }
172    }
173}
174
175/// A Haystack scientific notation Number, encapsulating a value and
176/// an optional unit value. The unit is represented as a
177/// string.
178#[derive(Clone, Debug, PartialEq)]
179pub struct ScientificNumber {
180    significand: f64,
181    exponent: i32,
182    unit: Option<String>,
183}
184
185impl ScientificNumber {
186    /// Create a new `ScientificNumber`. If present, the unit should
187    /// be a valid unit string from Project Haystack's
188    /// unit database. The significand must be a finite number which is
189    /// not NaN.
190    pub fn new(
191        significand: f64,
192        exponent: i32,
193        unit: Option<String>,
194    ) -> Option<Self> {
195        if significand.is_nan() || significand.is_infinite() {
196            None
197        } else {
198            Some(Self {
199                significand,
200                exponent,
201                unit,
202            })
203        }
204    }
205
206    /// Create a new `ScientificNumber` with no unit. The significand must
207    /// be a finite number which is not NaN.
208    pub fn new_unitless(significand: f64, exponent: i32) -> Option<Self> {
209        Self::new(significand, exponent, None)
210    }
211
212    /// Return the numeric significand component of this number.
213    pub fn significand(&self) -> f64 {
214        self.significand
215    }
216
217    /// Return the numeric exponent component of this number.
218    pub fn exponent(&self) -> i32 {
219        self.exponent
220    }
221
222    /// Return the unit component of this number, if present.
223    pub fn unit(&self) -> Option<&str> {
224        self.unit.as_ref().map(|unit| unit.as_ref())
225    }
226
227    /// Return a string containing Axon code representing this number.
228    pub fn to_axon_code(&self) -> String {
229        let exp = self.exponent();
230        let sig = self.significand();
231        if let Some(unit) = self.unit() {
232            format!("{}e{}{}", sig, exp, unit)
233        } else {
234            format!("{}e{}", sig, exp)
235        }
236    }
237}
238
239impl std::fmt::Display for ScientificNumber {
240    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
241        let exp = self.exponent();
242        let sig = self.significand();
243        if let Some(unit) = self.unit() {
244            write!(f, "{}e{} {}", sig, exp, unit)
245        } else {
246            write!(f, "{}e{}", sig, exp)
247        }
248    }
249}