Provide support for half-precision floats

dev
Alberto Restifo 2020-05-03 13:39:06 +02:00
parent d0ee8df1af
commit 332ca4af10
3 changed files with 30 additions and 29 deletions

View File

@ -5,22 +5,16 @@
This library implements the [RFC7049: Concise Binary Object Representation (CBOR)][rfc] This library implements the [RFC7049: Concise Binary Object Representation (CBOR)][rfc]
in Crystal. in Crystal.
**WARNING:** This library is still a work in progress.
## Features ## Features
- Full support for diagnostic notation - Full support for diagnostic notation
- Assign a field to a type base on the CBOR tag - Assign a field to a type base on the CBOR tag
- Support for a wide range of IANA CBOR Tags - Support for a wide range of IANA CBOR Tags (see below)
## Limitations ## Limitations
### Half-precision floating point is not supported
Crystal doesn't have a `Float16` type, so half-precision floating point numbers
are not supported for the time being.
If you know of a way to solve handle half-precision float, a contribution would
be really appreciated.
### Maximum Array/String array/Bytes array length ### Maximum Array/String array/Bytes array length
The spec allows for the maximum length of arrays, string arrays and bytes array The spec allows for the maximum length of arrays, string arrays and bytes array
@ -49,6 +43,15 @@ require "cbor"
TODO: Write usage instructions here TODO: Write usage instructions here
## Supported tags
All the tags specified in [section 2.4 of RFC 7049][rfc-tags] are supported
and the values are encoded in the respective Crystal types:
- `Time`
- `BigInt`
- `BigFloat`
## Development ## Development
TODO: Write development instructions here TODO: Write development instructions here
@ -66,3 +69,4 @@ TODO: Write development instructions here
- [Alberto Restifo](https://github.com/your-github-user) - creator and maintainer - [Alberto Restifo](https://github.com/your-github-user) - creator and maintainer
[rfc]: https://tools.ietf.org/html/rfc7049 [rfc]: https://tools.ietf.org/html/rfc7049
[rfc-tags]: https://tools.ietf.org/html/rfc7049#section-2.4

View File

@ -3,19 +3,17 @@ require "./spec_helper"
# All those tests have been exported from the RFC7049 appendix A. # All those tests have been exported from the RFC7049 appendix A.
tests = [ tests = [
# Disabled as half-precision floats are not supported:
{ %(0.0), "f9 00 00" }, { %(0.0), "f9 00 00" },
{ %(-0.0), "f9 80 00" }, { %(-0.0), "f9 80 00" },
{ %(1.0), "f9 3c 00" }, { %(1.0), "f9 3c 00" },
{ %(1.5), "f9 3e 00" }, { %(1.5), "f9 3e 00" },
{ %(65504.0), "f9 7b ff" }, { %(65504.0), "f9 7b ff" },
# { %(0.00006103515625), "f9 04 00" }, TODO: Something about the presentation { %(6.1035156e-5), "f9 04 00" },
{ %(-4.0), "f9 c4 00" }, { %(-4.0), "f9 c4 00" },
# { %(5.960464477539063e-8), "f9 00 01" }, { %(5.9604645e-8), "f9 00 01" },
# { %(Infinity), "f9 7c 00" }, { %(Infinity), "f9 7c 00" },
# { %(NaN), "f9 7e 00" }, { %(NaN), "f9 7e 00" },
# { %(-Infinity), "f9 fc 00" }, { %(-Infinity), "f9 fc 00" },
{ %(0), "00" }, { %(0), "00" },
{ %(1), "01" }, { %(1), "01" },
{ %(10), "0a" }, { %(10), "0a" },
@ -36,7 +34,7 @@ tests = [
{ %(-1000), "39 03 e7" }, { %(-1000), "39 03 e7" },
{ %(1.1), "fb 3f f1 99 99 99 99 99 9a" }, { %(1.1), "fb 3f f1 99 99 99 99 99 9a" },
{ %(100000.0), "fa 47 c3 50 00" }, { %(100000.0), "fa 47 c3 50 00" },
# { %(3.4028234663852886e+38), "fa 7f 7f ff ff" }, TODO: Not precise enough? { %(3.4028235e+38), "fa 7f 7f ff ff" },
{ %(1.0e+300), "fb 7e 37 e4 3c 88 00 75 9c" }, { %(1.0e+300), "fb 7e 37 e4 3c 88 00 75 9c" },
{ %(-4.1), "fb c0 10 66 66 66 66 66 66" }, { %(-4.1), "fb c0 10 66 66 66 66 66 66" },
{ %(Infinity), "fa 7f 80 00 00" }, { %(Infinity), "fa 7f 80 00 00" },

View File

@ -1,30 +1,29 @@
# Returns a Float32 by reading the 16 bit as a IEEE 754 half-precision floating # Reads the `UInt16` as a half-point floating point number
# point (Float16).
def Float32.new(i : UInt16) def Float32.new(i : UInt16)
# Check for signed zero # Check for signed zero
if i & 0x7FFF_u16 == 0 if i & 0x7FFF == 0
return (i.unsafe_as(UInt32) << 16).unsafe_as(Float32) return (i.unsafe_as(UInt32) << 16).unsafe_as(Float32)
end end
half_sign = (i & 0x8000_u16).unsafe_as(UInt32) half_sign = (i & 0x8000).to_u32
half_exp = (i & 0x7C00_u16).unsafe_as(UInt32) half_exp = (i & 0x7C00).to_u32
half_man = (i & 0x03FF_u16).unsafe_as(UInt32) half_man = (i & 0x03FF).to_u32
# Check for an infinity or NaN when all exponent bits set # Check for an infinity or NaN when all exponent bits set
if half_exp == 0x7C00_u32 if (i & 0x7C00) == 0x7C00
# Check for signed infinity if mantissa is zero # Check for signed infinity if mantissa is zero
if half_man == 0 if half_man == 0
return ((half_sign << 16) | 0x7F80_0000_u32).unsafe_as(Float32) return ((half_sign << 16) | 0x7F80_0000).unsafe_as(Float32)
else else
# NaN, keep current mantissa but also set most significiant mantissa bit # NaN, keep current mantissa but also set most significiant mantissa bit
return ((half_sign << 16) | 0x7FC0_0000_u32 | (half_man << 13)).unsafe_as(Float32) return ((half_sign << 16) | 0x7FC0_0000 | (half_man << 13)).unsafe_as(Float32)
end end
end end
# Calculate single-precision components with adjusted exponent # Calculate single-precision components with adjusted exponent
sign = half_sign << 16 sign = half_sign << 16
# Unbias exponent # Unbias exponent
unbiased_exp = ((half_exp.unsafe_as(Int32)) >> 10) - 15 unbiased_exp = (half_exp.unsafe_as(Int32) >> 10) - 15
# Check for subnormals, which will be normalized by adjusting exponent # Check for subnormals, which will be normalized by adjusting exponent
if half_exp == 0 if half_exp == 0
@ -33,12 +32,12 @@ def Float32.new(i : UInt16)
# Rebias and adjust exponent # Rebias and adjust exponent
exp = (127 - 15 - e) << 23 exp = (127 - 15 - e) << 23
man = (half_man << (14 + e)) & 0x7F_FF_FF_u32 man = (half_man << (14 + e)) & 0x7F_FF_FF
return (sign | exp | man).unsafe_as(Float32) return (sign | exp | man).unsafe_as(Float32)
end end
# Rebias exponent for a normalized normal # Rebias exponent for a normalized normal
exp = (unbiased_exp + 127).unsafe_as(UInt32) << 23 exp = (unbiased_exp + 127).unsafe_as(UInt32) << 23
man = (half_man & 0x03FF_u32) << 13 man = (half_man & 0x03FF) << 13
(sign | exp | man).unsafe_as(Float32) (sign | exp | man).unsafe_as(Float32)
end end