lilliput_core/value/
float.rs1use 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#[cfg_attr(any(test, feature = "testing"), derive(Arbitrary))]
12#[derive(Copy, Clone)]
13pub enum FloatValue {
14 F32(f32),
16 F64(f64),
18}
19
20impl FloatValue {
21 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 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}