diff --git a/spec/rfc_spec.cr b/spec/rfc_spec.cr index 534ed90..6eaace6 100644 --- a/spec/rfc_spec.cr +++ b/spec/rfc_spec.cr @@ -4,13 +4,13 @@ require "./spec_helper" tests = [ # Disabled as half-precision floats are not supported: - # { %(0.0), "f9 00 00" }, - # { %(-0.0), "f9 80 00" }, - # { %(1.0), "f9 3c 00" }, - # { %(1.5), "f9 3e 00" }, - # { %(65504.0), "f9 7b ff" }, - # { %(0.00006103515625), "f9 04 00" }, - # { %(-4.0), "f9 c4 00" }, + { %(0.0), "f9 00 00" }, + { %(-0.0), "f9 80 00" }, + { %(1.0), "f9 3c 00" }, + { %(1.5), "f9 3e 00" }, + { %(65504.0), "f9 7b ff" }, + # { %(0.00006103515625), "f9 04 00" }, TODO: Something about the presentation + { %(-4.0), "f9 c4 00" }, # { %(5.960464477539063e-8), "f9 00 01" }, # { %(Infinity), "f9 7c 00" }, # { %(NaN), "f9 7e 00" }, diff --git a/src/cbor/lexer.cr b/src/cbor/lexer.cr index 97d24d7..49e734d 100644 --- a/src/cbor/lexer.cr +++ b/src/cbor/lexer.cr @@ -84,7 +84,7 @@ class CBOR::Lexer when 0xe0..0xf8 consume_simple_value(read_size(byte - 0xe0)) when 0xf9 - raise ParseError.new("Half-precision floating point numbers are not supported") + Token::FloatT.new(value: Float32.new(read(UInt16))) when 0xfa Token::FloatT.new(value: read(Float32)) when 0xfb diff --git a/src/cbor/type/float_16.cr b/src/cbor/type/float_16.cr new file mode 100644 index 0000000..fa383e9 --- /dev/null +++ b/src/cbor/type/float_16.cr @@ -0,0 +1,44 @@ +# Returns a Float32 by reading the 16 bit as a IEEE 754 half-precision floating +# point (Float16). +def Float32.new(i : UInt16) + # Check for signed zero + if i & 0x7FFF_u16 == 0 + return (i.unsafe_as(UInt32) << 16).unsafe_as(Float32) + end + + half_sign = (i & 0x8000_u16).unsafe_as(UInt32) + half_exp = (i & 0x7C00_u16).unsafe_as(UInt32) + half_man = (i & 0x03FF_u16).unsafe_as(UInt32) + + # Check for an infinity or NaN when all exponent bits set + if half_exp == 0x7C00_u32 + # Check for signed infinity if mantissa is zero + if half_man == 0 + return ((half_sign << 16) | 0x7F80_0000_u32).unsafe_as(Float32) + else + # NaN, keep current mantissa but also set most significiant mantissa bit + return ((half_sign << 16) | 0x7FC0_0000_u32 | (half_man << 13)).unsafe_as(Float32) + end + end + + # Calculate single-precision components with adjusted exponent + sign = half_sign << 16 + # Unbias exponent + unbiased_exp = ((half_exp.unsafe_as(Int32)) >> 10) - 15 + + # Check for subnormals, which will be normalized by adjusting exponent + if half_exp == 0 + # Calculate how much to adjust the exponent by + e = half_man.unsafe_as(UInt16).leading_zeros_count - 6 + + # Rebias and adjust exponent + exp = (127 - 15 - e) << 23 + man = (half_man << (14 + e)) & 0x7F_FF_FF_u32 + return (sign | exp | man).unsafe_as(Float32) + end + + # Rebias exponent for a normalized normal + exp = (unbiased_exp + 127).unsafe_as(UInt32) << 23 + man = (half_man & 0x03FF_u32) << 13 + (sign | exp | man).unsafe_as(Float32) +end