Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ The CBOR Event library. There are no dependencies, all platforms are supported a
"""

[dev-dependencies]
half = "2.7.1"
quickcheck = "0.7"
76 changes: 74 additions & 2 deletions src/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,37 @@ use len::{Len, LenSz, StringLenSz, Sz};
use result::Result;
use types::{Special, Type};

/// Decode an IEEE 754 binary16 (half-precision) bit pattern to f64,
/// per RFC 8949 §3.3 / Appendix D.
/// Lossless: every f16 value is exactly representable in f64.
///
/// Vendored because rust's native f16 is not yet stable (rust-lang/rust#116909)
/// and this crate has no dependencies.
/// Once stable, replace this code with https://github.com/primetype/cbor_event/pull/18
fn f16_bits_to_f64(bits: u16) -> f64 {
let exp = (bits >> 10) & 0x1f;
let mant = (bits & 0x3ff) as f64;
let mag = match exp {
// subnormal or ±0: mant * 2^-24
0 => mant * (1.0 / 16_777_216.0),
0x1f => {
if mant == 0.0 {
f64::INFINITY
} else {
f64::NAN
}
}
// normal: (1024 + mant) * 2^(exp - 25), built via from_bits
// since f64::powi/exp2 are std-only and this crate is no_std
_ => (1024.0 + mant) * f64::from_bits(u64::from(exp + 1023 - 25) << 52),
};
if bits & 0x8000 != 0 {
-mag
} else {
mag
}
}

pub trait Deserialize: Sized {
/// method to implement to deserialise an object from the given
/// `Deserializer`.
Expand Down Expand Up @@ -772,9 +803,9 @@ impl Deserializer {
Ok(Special::Unassigned(b as u8))
}
0x19 => {
let f = self.u16(1)?;
let f = self.u16(1)? as u16;
self.advance(3)?;
Ok(Special::Float(f as f64))
Ok(Special::Float(f16_bits_to_f64(f)))
}
0x1a => {
let f = self.u32(1)? as u32;
Expand Down Expand Up @@ -964,6 +995,47 @@ mod test {
assert_eq!(float, 1.1);
}

#[test]
fn float16() {
// (bytes, expected) pairs from RFC 8949 Appendix A
let cases: &[(&[u8], f64)] = &[
(&[0xf9, 0x3c, 0x00], 1.0),
(&[0xf9, 0x42, 0x00], 3.0),
(&[0xf9, 0x3e, 0x00], 1.5),
(&[0xf9, 0x00, 0x00], 0.0),
(&[0xf9, 0x80, 0x00], -0.0),
(&[0xf9, 0xc4, 0x00], -4.0),
(&[0xf9, 0x00, 0x01], 5.960464477539063e-8), // subnormal
(&[0xf9, 0x04, 0x00], 6.103515625e-5),
(&[0xf9, 0x7b, 0xff], 65504.0),
(&[0xf9, 0x7c, 0x00], f64::INFINITY),
(&[0xf9, 0xfc, 0x00], f64::NEG_INFINITY),
];
for (bytes, expected) in cases {
let mut raw = Deserializer::from(bytes.to_vec());
assert_eq!(raw.float().unwrap(), *expected, "bytes: {:x?}", bytes);
}
let mut raw = Deserializer::from(vec![0xf9, 0x7e, 0x00]);
assert!(raw.float().unwrap().is_nan());
}

#[test]
fn float16_exhaustive_against_half_crate() {
for bits in 0..=u16::MAX {
let ours = super::f16_bits_to_f64(bits);
let reference = half::f16::from_bits(bits).to_f64();
// bit-exact so ±0.0 are distinguished; NaN payloads are exempt
// (we canonicalize to f64::NAN, half preserves the payload)
assert!(
ours.to_bits() == reference.to_bits() || (ours.is_nan() && reference.is_nan()),
"bits {:#06x}: ours {} != half {}",
bits,
ours,
reference
);
}
}

#[test]
fn float32() {
let vec = vec![0xfa, 0x47, 0xc3, 0x50, 0x00];
Expand Down
Loading