dcbor/
float.rs

1import_stdlib!();
2
3use half::f16;
4
5use super::varint::{EncodeVarInt, MajorType};
6use crate::{CBOR, CBORCase, Error, ExactFrom, Result, Simple, int::From64};
7
8/// # Floating Point Number Support in dCBOR
9///
10/// dCBOR provides canonical encoding for floating point values through
11/// implementation of the `From<T>` and `TryFrom<CBOR>` traits for `f16`, `f32`,
12/// and `f64` types.
13///
14/// Per the dCBOR specification, the canonical encoding rules ensure
15/// deterministic representation:
16///
17/// - Numeric reduction: Floating point values with zero fractional part in
18///   range [-2^63, 2^64-1] are automatically encoded as integers (e.g., 42.0
19///   becomes 42)
20/// - Values are encoded in the smallest possible representation that preserves
21///   their value
22/// - All NaN values are canonicalized to a single representation: 0xf97e00
23/// - Positive/negative infinity are canonicalized to half-precision
24///   representations
25///
26/// ## Example
27///
28/// ```
29/// use dcbor::prelude::*;
30///
31/// // Create CBOR from floating point values using `into()`
32/// let cbor_integer: CBOR = 42.0.into();  // Numeric reduction: encoded as integer 42
33/// let cbor_float: CBOR = 3.14159.into();  // Encoded as floating point
34/// let cbor_nan: CBOR = f64::NAN.into();  // Canonicalized to 0xf97e00
35///
36/// // Convert back to floating point
37/// let value_integer: f64 = cbor_integer.try_into().unwrap();
38/// assert_eq!(value_integer, 42.0);
39///
40/// // Maps can use numeric keys with automatic reduction
41/// let mut map = Map::new();
42/// map.insert(1.0, "integer key");    // 1.0 becomes 1
43/// map.insert(2, "another key");      // Integer directly
44///
45/// // Verify numeric reduction in maps
46/// let key_value: String = map.extract::<i32, String>(1).unwrap();
47/// assert_eq!(key_value, "integer key");
48/// ```
49static CBOR_NAN: [u8; 3] = [0xf9, 0x7e, 0x00];
50
51impl From<f64> for CBOR {
52    fn from(value: f64) -> Self {
53        let n = value;
54        if n < 0.0f64
55            && let Some(n) = i128::exact_from_f64(n)
56            && let Some(i) = u64::exact_from_i128(-1 - n)
57        {
58            return CBORCase::Negative(i).into();
59        }
60        if let Some(i) = u64::exact_from_f64(n) {
61            return i.into();
62        }
63        CBORCase::Simple(Simple::Float(n)).into()
64    }
65}
66
67pub(crate) fn f64_cbor_data(value: f64) -> Vec<u8> {
68    let n = value;
69    let f = n as f32;
70    if (f as f64) == n {
71        return f32_cbor_data(f);
72    }
73    if n < 0.0f64
74        && let Some(n) = i128::exact_from_f64(n)
75        && let Some(i) = u64::exact_from_i128(-1 - n)
76    {
77        let cbor: CBOR = CBORCase::Negative(i).into();
78        return cbor.to_cbor_data();
79    }
80    if let Some(i) = u64::exact_from_f64(n) {
81        return i.cbor_data();
82    }
83    if value.is_nan() {
84        return CBOR_NAN.to_vec();
85    }
86    n.to_bits().encode_int(MajorType::Simple)
87}
88
89pub(crate) fn validate_canonical_f64(n: f64) -> Result<()> {
90    if n == (n as f32 as f64) || n == (n as i64 as f64) || n.is_nan() {
91        return Err(Error::NonCanonicalNumeric);
92    }
93    Ok(())
94}
95
96impl TryFrom<CBOR> for f64 {
97    type Error = Error;
98
99    fn try_from(cbor: CBOR) -> Result<Self> {
100        match cbor.into_case() {
101            CBORCase::Unsigned(n) => {
102                if let Some(f) = f64::exact_from_u64(n) {
103                    Ok(f)
104                } else {
105                    Err(Error::OutOfRange)
106                }
107            }
108            CBORCase::Negative(n) => {
109                if let Some(f) = f64::exact_from_u64(n) {
110                    Ok(-1f64 - f)
111                } else {
112                    Err(Error::OutOfRange)
113                }
114            }
115            CBORCase::Simple(Simple::Float(n)) => Ok(n),
116            _ => Err(Error::WrongType),
117        }
118    }
119}
120
121impl From<f32> for CBOR {
122    fn from(value: f32) -> Self {
123        let n = value;
124        if n < 0.0f32
125            && let Some(i) = u64::exact_from_f32(-1f32 - n)
126        {
127            return CBORCase::Negative(i).into();
128        }
129        if let Some(i) = u32::exact_from_f32(n) {
130            return i.into();
131        }
132        CBORCase::Simple(Simple::Float(n as f64)).into()
133    }
134}
135
136pub(crate) fn f32_cbor_data(value: f32) -> Vec<u8> {
137    let n = value;
138    let f = f16::from_f32(n);
139    if f.to_f32() == n {
140        return f16_cbor_data(f);
141    }
142    if n < 0.0f32
143        && let Some(i) = u64::exact_from_f32(-1f32 - n)
144    {
145        let cbor: CBOR = CBORCase::Negative(i).into();
146        return cbor.to_cbor_data();
147    }
148    if let Some(i) = u32::exact_from_f32(n) {
149        return i.cbor_data();
150    }
151    if value.is_nan() {
152        return CBOR_NAN.to_vec();
153    }
154    n.to_bits().encode_int(MajorType::Simple)
155}
156
157pub(crate) fn validate_canonical_f32(n: f32) -> Result<()> {
158    if n == f16::from_f32(n).to_f32() || n == (n as i32 as f32) || n.is_nan() {
159        return Err(Error::NonCanonicalNumeric);
160    }
161    Ok(())
162}
163
164impl TryFrom<CBOR> for f32 {
165    type Error = Error;
166
167    fn try_from(cbor: CBOR) -> Result<Self> {
168        match cbor.into_case() {
169            CBORCase::Unsigned(n) => {
170                if let Some(f) = f32::exact_from_u64(n) {
171                    Ok(f)
172                } else {
173                    Err(Error::OutOfRange)
174                }
175            }
176            CBORCase::Negative(n) => {
177                if let Some(f) = f32::exact_from_u64(n) {
178                    Ok(f)
179                } else {
180                    Err(Error::OutOfRange)
181                }
182            }
183            CBORCase::Simple(Simple::Float(n)) => {
184                if let Some(f) = f32::exact_from_f64(n) {
185                    Ok(f)
186                } else {
187                    Err(Error::OutOfRange)
188                }
189            }
190            _ => Err(Error::WrongType),
191        }
192    }
193}
194
195impl From<f16> for CBOR {
196    fn from(value: f16) -> Self {
197        let n = value.to_f64();
198        if n < 0.0
199            && let Some(i) = u64::exact_from_f64(-1f64 - n)
200        {
201            return CBORCase::Negative(i).into();
202        }
203        if let Some(i) = u16::exact_from_f64(n) {
204            return i.into();
205        }
206        CBORCase::Simple(Simple::Float(n)).into()
207    }
208}
209
210pub(crate) fn f16_cbor_data(value: f16) -> Vec<u8> {
211    let n = value.to_f64();
212    if n < 0.0
213        && let Some(i) = u64::exact_from_f64(-1f64 - n)
214    {
215        let cbor: CBOR = CBORCase::Negative(i).into();
216        return cbor.to_cbor_data();
217    }
218    if let Some(i) = u16::exact_from_f64(n) {
219        return i.cbor_data();
220    }
221    if value.is_nan() {
222        return CBOR_NAN.to_vec();
223    }
224    value.to_bits().encode_int(MajorType::Simple)
225}
226
227impl TryFrom<CBOR> for f16 {
228    type Error = Error;
229
230    fn try_from(cbor: CBOR) -> Result<Self> {
231        match cbor.into_case() {
232            CBORCase::Unsigned(n) => {
233                if let Some(f) = f16::exact_from_u64(n) {
234                    Ok(f)
235                } else {
236                    Err(Error::OutOfRange)
237                }
238            }
239            CBORCase::Negative(n) => {
240                if let Some(f) = f64::exact_from_u64(n) {
241                    if let Some(b) = f16::exact_from_f64(-1f64 - f) {
242                        Ok(b)
243                    } else {
244                        Err(Error::OutOfRange)
245                    }
246                } else {
247                    Err(Error::OutOfRange)
248                }
249            }
250            CBORCase::Simple(Simple::Float(n)) => {
251                if let Some(f) = f16::exact_from_f64(n) {
252                    Ok(f)
253                } else {
254                    Err(Error::OutOfRange)
255                }
256            }
257            _ => Err(Error::WrongType),
258        }
259    }
260}
261
262pub(crate) fn validate_canonical_f16(n: f16) -> Result<()> {
263    let f = n.to_f64();
264    if f == (f as i64 as f64) || (n.is_nan() && n.to_bits() != 0x7e00) {
265        return Err(Error::NonCanonicalNumeric);
266    }
267    Ok(())
268}