oxsdatatypes/
float.rs

1use crate::{Boolean, Double, Integer};
2use std::cmp::Ordering;
3use std::fmt;
4use std::num::ParseFloatError;
5use std::ops::{Add, Div, Mul, Neg, Sub};
6use std::str::FromStr;
7
8/// [XML Schema `float` datatype](https://www.w3.org/TR/xmlschema11-2/#float)
9///
10/// Uses internally a [`f32`].
11///
12/// <div class="warning">Serialization does not follow the canonical mapping.</div>
13#[derive(Debug, Clone, Copy, Default, PartialEq)]
14#[repr(transparent)]
15pub struct Float {
16    value: f32,
17}
18
19impl Float {
20    pub const INFINITY: Self = Self {
21        value: f32::INFINITY,
22    };
23    pub const MAX: Self = Self { value: f32::MAX };
24    pub const MIN: Self = Self { value: f32::MIN };
25    pub const NAN: Self = Self { value: f32::NAN };
26    pub const NEG_INFINITY: Self = Self {
27        value: f32::NEG_INFINITY,
28    };
29
30    #[inline]
31    #[must_use]
32    pub fn from_be_bytes(bytes: [u8; 4]) -> Self {
33        Self {
34            value: f32::from_be_bytes(bytes),
35        }
36    }
37
38    #[inline]
39    #[must_use]
40    pub fn to_be_bytes(self) -> [u8; 4] {
41        self.value.to_be_bytes()
42    }
43
44    /// [fn:abs](https://www.w3.org/TR/xpath-functions-31/#func-abs)
45    #[inline]
46    #[must_use]
47    pub fn abs(self) -> Self {
48        self.value.abs().into()
49    }
50
51    /// [fn:ceiling](https://www.w3.org/TR/xpath-functions-31/#func-ceiling)
52    #[inline]
53    #[must_use]
54    pub fn ceil(self) -> Self {
55        self.value.ceil().into()
56    }
57
58    /// [fn:floor](https://www.w3.org/TR/xpath-functions-31/#func-floor)
59    #[inline]
60    #[must_use]
61    pub fn floor(self) -> Self {
62        self.value.floor().into()
63    }
64
65    /// [fn:round](https://www.w3.org/TR/xpath-functions-31/#func-round)
66    #[inline]
67    #[must_use]
68    pub fn round(self) -> Self {
69        self.value.round().into()
70    }
71
72    #[inline]
73    #[must_use]
74    pub fn is_nan(self) -> bool {
75        self.value.is_nan()
76    }
77
78    #[inline]
79    #[must_use]
80    pub fn is_finite(self) -> bool {
81        self.value.is_finite()
82    }
83
84    /// Checks if the two values are [identical](https://www.w3.org/TR/xmlschema11-2/#identity).
85    #[inline]
86    #[must_use]
87    pub fn is_identical_with(self, other: Self) -> bool {
88        self.value.to_bits() == other.value.to_bits()
89    }
90}
91
92impl From<Float> for f32 {
93    #[inline]
94    fn from(value: Float) -> Self {
95        value.value
96    }
97}
98
99impl From<Float> for f64 {
100    #[inline]
101    fn from(value: Float) -> Self {
102        value.value.into()
103    }
104}
105
106impl From<f32> for Float {
107    #[inline]
108    fn from(value: f32) -> Self {
109        Self { value }
110    }
111}
112
113impl From<i8> for Float {
114    #[inline]
115    fn from(value: i8) -> Self {
116        Self {
117            value: value.into(),
118        }
119    }
120}
121
122impl From<i16> for Float {
123    #[inline]
124    fn from(value: i16) -> Self {
125        Self {
126            value: value.into(),
127        }
128    }
129}
130
131impl From<u8> for Float {
132    #[inline]
133    fn from(value: u8) -> Self {
134        Self {
135            value: value.into(),
136        }
137    }
138}
139
140impl From<u16> for Float {
141    #[inline]
142    fn from(value: u16) -> Self {
143        Self {
144            value: value.into(),
145        }
146    }
147}
148
149impl From<Boolean> for Float {
150    #[inline]
151    fn from(value: Boolean) -> Self {
152        f32::from(bool::from(value)).into()
153    }
154}
155
156impl From<Integer> for Float {
157    #[inline]
158    #[allow(clippy::cast_precision_loss)]
159    fn from(value: Integer) -> Self {
160        (i64::from(value) as f32).into()
161    }
162}
163
164impl From<Double> for Float {
165    #[inline]
166    #[allow(clippy::cast_possible_truncation)]
167    fn from(value: Double) -> Self {
168        Self {
169            value: f64::from(value) as f32,
170        }
171    }
172}
173
174impl FromStr for Float {
175    type Err = ParseFloatError;
176
177    #[inline]
178    fn from_str(input: &str) -> Result<Self, Self::Err> {
179        Ok(f32::from_str(input)?.into())
180    }
181}
182
183impl fmt::Display for Float {
184    #[inline]
185    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186        if self.value == f32::INFINITY {
187            f.write_str("INF")
188        } else if self.value == f32::NEG_INFINITY {
189            f.write_str("-INF")
190        } else {
191            self.value.fmt(f)
192        }
193    }
194}
195
196impl PartialOrd for Float {
197    #[inline]
198    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
199        self.value.partial_cmp(&other.value)
200    }
201}
202
203impl Neg for Float {
204    type Output = Self;
205
206    #[inline]
207    fn neg(self) -> Self {
208        (-self.value).into()
209    }
210}
211
212impl Add for Float {
213    type Output = Self;
214
215    #[inline]
216    fn add(self, rhs: Self) -> Self {
217        (self.value + rhs.value).into()
218    }
219}
220
221impl Sub for Float {
222    type Output = Self;
223
224    #[inline]
225    fn sub(self, rhs: Self) -> Self {
226        (self.value - rhs.value).into()
227    }
228}
229
230impl Mul for Float {
231    type Output = Self;
232
233    #[inline]
234    fn mul(self, rhs: Self) -> Self {
235        (self.value * rhs.value).into()
236    }
237}
238
239impl Div for Float {
240    type Output = Self;
241
242    #[inline]
243    fn div(self, rhs: Self) -> Self {
244        (self.value / rhs.value).into()
245    }
246}
247
248#[cfg(test)]
249#[allow(clippy::panic_in_result_fn)]
250mod tests {
251    use super::*;
252
253    #[test]
254    fn eq() {
255        assert_eq!(Float::from(0.), Float::from(0.));
256        assert_ne!(Float::NAN, Float::NAN);
257        assert_eq!(Float::from(-0.), Float::from(0.));
258    }
259
260    #[test]
261    fn cmp() {
262        assert_eq!(
263            Float::from(0.).partial_cmp(&Float::from(0.)),
264            Some(Ordering::Equal)
265        );
266        assert_eq!(
267            Float::INFINITY.partial_cmp(&Float::MAX),
268            Some(Ordering::Greater)
269        );
270        assert_eq!(
271            Float::NEG_INFINITY.partial_cmp(&Float::MIN),
272            Some(Ordering::Less)
273        );
274        assert_eq!(Float::NAN.partial_cmp(&Float::from(0.)), None);
275        assert_eq!(Float::NAN.partial_cmp(&Float::NAN), None);
276        assert_eq!(
277            Float::from(0.).partial_cmp(&Float::from(-0.)),
278            Some(Ordering::Equal)
279        );
280    }
281
282    #[test]
283    fn is_identical_with() {
284        assert!(Float::from(0.).is_identical_with(Float::from(0.)));
285        assert!(Float::NAN.is_identical_with(Float::NAN));
286        assert!(!Float::from(-0.).is_identical_with(Float::from(0.)));
287    }
288
289    #[test]
290    fn from_str() -> Result<(), ParseFloatError> {
291        assert_eq!(Float::from_str("NaN")?.to_string(), "NaN");
292        assert_eq!(Float::from_str("INF")?.to_string(), "INF");
293        assert_eq!(Float::from_str("+INF")?.to_string(), "INF");
294        assert_eq!(Float::from_str("-INF")?.to_string(), "-INF");
295        assert_eq!(Float::from_str("0.0E0")?.to_string(), "0");
296        assert_eq!(Float::from_str("-0.0E0")?.to_string(), "-0");
297        assert_eq!(Float::from_str("0.1e1")?.to_string(), "1");
298        assert_eq!(Float::from_str("-0.1e1")?.to_string(), "-1");
299        assert_eq!(Float::from_str("1.e1")?.to_string(), "10");
300        assert_eq!(Float::from_str("-1.e1")?.to_string(), "-10");
301        assert_eq!(Float::from_str("1")?.to_string(), "1");
302        assert_eq!(Float::from_str("-1")?.to_string(), "-1");
303        assert_eq!(Float::from_str("1.")?.to_string(), "1");
304        assert_eq!(Float::from_str("-1.")?.to_string(), "-1");
305        assert_eq!(Float::from_str(&f32::MIN.to_string())?, Float::MIN);
306        assert_eq!(Float::from_str(&f32::MAX.to_string())?, Float::MAX);
307        Ok(())
308    }
309}