lilliput_core/value/
float.rs

1use std::hash::{Hash, Hasher};
2
3#[cfg(any(test, feature = "testing"))]
4use proptest::prelude::*;
5#[cfg(any(test, feature = "testing"))]
6use proptest_derive::Arbitrary;
7
8use decorum::{constraint::IsFloat, proxy::Constrained};
9
10/// Represents a floating-point number.
11#[cfg_attr(any(test, feature = "testing"), derive(Arbitrary))]
12#[derive(Copy, Clone)]
13pub enum FloatValue {
14    /// 32-bit value.
15    F32(f32),
16    /// 64-bit value.
17    F64(f64),
18}
19
20impl FloatValue {
21    /// Returns the value as a `f32`.
22    pub fn as_f32(self) -> f32 {
23        match self {
24            FloatValue::F32(value) => value,
25            FloatValue::F64(value) => value as f32,
26        }
27    }
28
29    /// Returns the value as a `f64`.
30    pub fn as_f64(self) -> f64 {
31        match self {
32            FloatValue::F32(value) => value as f64,
33            FloatValue::F64(value) => value,
34        }
35    }
36}
37
38impl Default for FloatValue {
39    fn default() -> Self {
40        Self::F32(0.0)
41    }
42}
43
44impl From<f32> for FloatValue {
45    fn from(value: f32) -> Self {
46        Self::F32(value)
47    }
48}
49
50impl From<f64> for FloatValue {
51    fn from(value: f64) -> Self {
52        Self::F64(value)
53    }
54}
55
56impl From<FloatValue> for f32 {
57    fn from(value: FloatValue) -> Self {
58        value.as_f32()
59    }
60}
61
62impl From<FloatValue> for f64 {
63    fn from(value: FloatValue) -> Self {
64        value.as_f64()
65    }
66}
67
68impl Eq for FloatValue {}
69
70impl PartialEq for FloatValue {
71    fn eq(&self, other: &Self) -> bool {
72        self.canonical_total().eq(&other.canonical_total())
73    }
74}
75
76impl Ord for FloatValue {
77    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
78        self.canonical_total().cmp(&other.canonical_total())
79    }
80}
81
82impl PartialOrd for FloatValue {
83    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
84        Some(self.cmp(other))
85    }
86}
87
88impl Hash for FloatValue {
89    fn hash<H: Hasher>(&self, state: &mut H) {
90        self.canonical_total().hash(state);
91    }
92}
93
94impl std::fmt::Debug for FloatValue {
95    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96        if f.alternate() {
97            match self {
98                Self::F32(value) => write!(f, "{value:#?}_f32"),
99                Self::F64(value) => write!(f, "{value:#?}_f64"),
100            }
101        } else {
102            match self {
103                Self::F32(value) => std::fmt::Debug::fmt(value, f),
104                Self::F64(value) => std::fmt::Debug::fmt(value, f),
105            }
106        }
107    }
108}
109
110impl std::fmt::Display for FloatValue {
111    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112        match self {
113            Self::F32(value) => std::fmt::Display::fmt(value, f),
114            Self::F64(value) => std::fmt::Display::fmt(value, f),
115        }
116    }
117}
118
119#[cfg(feature = "serde")]
120impl serde::Serialize for FloatValue {
121    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
122    where
123        S: serde::Serializer,
124    {
125        match self {
126            Self::F32(value) => value.serialize(serializer),
127            Self::F64(value) => value.serialize(serializer),
128        }
129    }
130}
131
132#[cfg(feature = "serde")]
133impl<'de> serde::Deserialize<'de> for FloatValue {
134    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
135    where
136        D: serde::Deserializer<'de>,
137    {
138        struct ValueVisitor;
139
140        impl serde::de::Visitor<'_> for ValueVisitor {
141            type Value = FloatValue;
142
143            fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
144                formatter.write_str("floating-point value")
145            }
146
147            #[inline]
148            fn visit_f32<E>(self, value: f32) -> Result<Self::Value, E> {
149                Ok(value.into())
150            }
151
152            #[inline]
153            fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E> {
154                Ok(value.into())
155            }
156        }
157
158        deserializer.deserialize_any(ValueVisitor)
159    }
160}
161
162impl FloatValue {
163    fn canonical_total(self) -> Constrained<f64, IsFloat> {
164        decorum::Total::assert(self.as_f64())
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use proptest::prelude::*;
171    use test_log::test;
172
173    use crate::{
174        config::{EncoderConfig, PackingMode},
175        decoder::Decoder,
176        encoder::Encoder,
177        io::{SliceReader, VecWriter},
178        value::Value,
179    };
180
181    use super::*;
182
183    fn non_normal_or_subnormal_f32() -> impl Strategy<Value = f32> {
184        proptest::prop_oneof![
185            proptest::num::f32::SIGNALING_NAN,
186            proptest::num::f32::QUIET_NAN,
187            proptest::num::f32::INFINITE,
188            proptest::num::f32::ZERO,
189        ]
190    }
191
192    fn non_normal_or_subnormal_f64() -> impl Strategy<Value = f64> {
193        proptest::prop_oneof![
194            proptest::num::f64::SIGNALING_NAN,
195            proptest::num::f64::QUIET_NAN,
196            proptest::num::f64::INFINITE,
197            proptest::num::f64::ZERO,
198        ]
199    }
200
201    #[test]
202    fn display() {
203        assert_eq!(format!("{}", FloatValue::from(4.2_f32)), "4.2");
204        assert_eq!(format!("{}", FloatValue::from(4.2_f64)), "4.2");
205    }
206
207    #[test]
208    fn debug() {
209        assert_eq!(format!("{:?}", FloatValue::from(4.2_f32)), "4.2");
210        assert_eq!(format!("{:?}", FloatValue::from(4.2_f64)), "4.2");
211
212        assert_eq!(format!("{:#?}", FloatValue::from(4.2_f32)), "4.2_f32");
213        assert_eq!(format!("{:#?}", FloatValue::from(4.2_f64)), "4.2_f64");
214    }
215
216    proptest! {
217        #[test]
218        fn encode_decode_roundtrip(value in FloatValue::arbitrary(), config in EncoderConfig::arbitrary()) {
219            let mut encoded: Vec<u8> = Vec::new();
220            let writer = VecWriter::new(&mut encoded);
221            let mut encoder = Encoder::new(writer, config);
222            encoder.encode_float_value(&value).unwrap();
223
224            prop_assert!(encoded.len() <= 1 + 8);
225
226            let reader = SliceReader::new(&encoded);
227            let mut decoder = Decoder::from_reader(reader);
228            let decoded = decoder.decode_float_value().unwrap();
229            prop_assert_eq!(&decoded, &value);
230
231            let reader = SliceReader::new(&encoded);
232            let mut decoder = Decoder::from_reader(reader);
233            let decoded = decoder.decode_value().unwrap();
234            let Value::Float(decoded) = decoded else {
235                panic!("expected float value");
236            };
237            prop_assert_eq!(&decoded, &value);
238        }
239
240        #[test]
241        fn non_normal_or_subnormal_f32_encodes_optimally(value in non_normal_or_subnormal_f32()) {
242            let config = EncoderConfig::default().with_packing(PackingMode::Optimal);
243
244            let mut encoded: Vec<u8> = Vec::new();
245            let writer = VecWriter::new(&mut encoded);
246            let mut encoder = Encoder::new(writer, config);
247            encoder.encode_f32(value).unwrap();
248
249            prop_assert!(encoded.len() == 2, "value should optimally pack to single byte");
250        }
251
252        #[test]
253        fn non_normal_or_subnormal_f64_encodes_optimally(value in non_normal_or_subnormal_f64()) {
254            let config = EncoderConfig::default().with_packing(PackingMode::Optimal);
255
256            let mut encoded: Vec<u8> = Vec::new();
257            let writer = VecWriter::new(&mut encoded);
258            let mut encoder = Encoder::new(writer, config);
259            encoder.encode_f64(value).unwrap();
260
261            prop_assert!(encoded.len() == 2, "value should optimally pack to single byte");
262        }
263    }
264}