Repository: fxamacker/cbor
Pull request #83 (2019-12-31 21:14:20)

Update README.md
From: There are no external dependencies and no code gen.
To: No code gen and the only import is cbor-go/float16 which is maintained by the same team.

codecov-io: # [Codecov](https://codecov.io/gh/fxamacker/cbor/pull/83?src=pr&el=h1) Report
> Merging [#83](https://codecov.io/gh/fxamacker/cbor/pull/83?src=pr&el=desc) into [master](https://codecov.io/gh/fxamacker/cbor/commit/fa1a91f55ab6a6e7ca4a9e537ace17f3c8eef4e6?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/fxamacker/cbor/pull/83/graphs/tree.svg?width=650&token=Tf4tnpMotO&height=150&src=pr)](https://codecov.io/gh/fxamacker/cbor/pull/83?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##           master      #83   +/-   ##
=======================================
  Coverage   97.11%   97.11%           
=======================================
  Files           6        6           
  Lines        1421     1421           
=======================================
  Hits         1380     1380           
  Misses         28       28           
  Partials       13       13
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/fxamacker/cbor/pull/83?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/fxamacker/cbor/pull/83?src=pr&el=footer). Last update [fa1a91f...621f984](https://codecov.io/gh/fxamacker/cbor/pull/83?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/README.md b/README.md
index 1c4d41e..266b79b 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ __What is CBOR__?  [CBOR](CBOR_GOLANG.md) ([RFC 7049](https://tools.ietf.org/htm
 
 __Why this CBOR library?__ It doesn't crash and it has well-balanced qualities: small, fast, safe and easy. 
 
-* __Small__ and self-contained.  Same programs are 4-9 MB smaller by switching to this library.  There are no external dependencies and no code gen.   
+* __Small__.  Same programs are 4-9 MB smaller by switching to this library.  No code gen and the only imported pkg is [cbor-go/float16](https://github.com/cbor-go/float16) which is maintained by the same team.
 
 * __Fast__. v1.3 became faster than a well-known library that uses `unsafe` optimizations and code gen.  Faster libraries will always exist, but speed is only one factor.  This library doesn't use `unsafe` optimizations or code gen.  
 
@@ -68,7 +68,7 @@ Recent activity:
 * [x] [Release v1.3.4](https://github.com/fxamacker/cbor/releases) -- bugfixes and refactoring.  Limit nested levels to 32 for arrays, maps, tags.
 * [x] [Release v1.4](https://github.com/fxamacker/cbor/releases) -- (latest) Deprecate bool encoding options and add int SortMode.  Use float16 to float32 conversion func that had all 65536 results verified to be correct. Fix decoding of float16 subnormal numbers.
 
-Coming soon: support for CBOR tags (major type 6) and new encoding option to shrink floats. After that, options for handling duplicate map keys.
+Coming soon: support for CBOR tags (major type 6). After that, options for handling duplicate map keys.
 
 ## Design Goals 
 This library is designed to be a generic CBOR encoder and decoder.  It was initially created for a [WebAuthn (FIDO2) server library](https://github.com/fxamacker/webauthn), because existing CBOR libraries (in Go) didn't meet certain criteria in 2019.
@@ -76,7 +76,7 @@ This library is designed to be a generic CBOR encoder and decoder.  It was initi
 This library is designed to be:
 
 * __Easy__ – API is like `encoding/json` plus `keyasint` and `toarray` struct tags.
-* __Small and self-contained__ – no external dependencies and no code gen.  Programs in cisco/senml are 4 MB smaller by switching to this library. In extreme cases programs can be smaller by 9+ MB. See [comparisons](#comparisons).
+* __Small__ – Programs in cisco/senml are 4 MB smaller by switching to this library. In extreme cases programs can be smaller by 9+ MB. No code gen and the only imported pkg is [cbor-go/float16](https://github.com/cbor-go/float16) which is maintained by the same team.  See [comparisons](#comparisons).
 * __Safe and reliable__ – no `unsafe` pkg, coverage >95%, coverage-guided fuzzing, and data validation to avoid crashes on malformed or malicious data.  See [comparisons](#comparisons).
 
 Competing factors are balanced:
@@ -98,7 +98,8 @@ Features not in Go's standard library are usually not added.  However, the __`to
 * Support "cbor" and "json" keys in Go's struct tags. If both are specified, then "cbor" is used.
 * `toarray` struct tag allows named struct fields for elements of CBOR arrays.
 * `keyasint` struct tag allows named struct fields for elements of CBOR maps with int keys.
-* Support 3 encoding modes: default (unsorted), Canonical, CTAP2Canonical.
+* Encoder has 3 sorting modes: default (unsorted), Canonical, CTAP2Canonical.
+* Encoder has 4 floating-point modes: default (no conversion), ShortestFloat16, ShortestFloat32, ShortestFloat64.
 * Support `encoding.BinaryMarshaler` and `encoding.BinaryUnmarshaler` interfaces.
 * Support `cbor.RawMessage` which can delay CBOR decoding or precompute CBOR encoding.
 * Support `cbor.Marshaler` and `cbor.Unmarshaler` interfaces to allow user-defined types to have custom CBOR encoding and decoding.
@@ -111,20 +112,27 @@ Features not in Go's standard library are usually not added.  However, the __`to
 * Decoder uses case-insensitive field name match when decoding to structs. 
 * Both encoder and decoder correctly handles nil slice, map, pointer, and interface values.
 
-Coming soon: support for CBOR tags (major type 6) and new encoding option to shrink floats.  After that, options for handling duplicate map keys.
+Coming soon: support for CBOR tags (major type 6).  After that, options for handling duplicate map keys.
 
 ## Standards
 This library implements CBOR as specified in [RFC 7049](https://tools.ietf.org/html/rfc7049) with minor [limitations](#limitations).
 
-Encoder has 3 modes:
+Encoder has options that can be combined to provide different encoding modes.
+
+Encoder has 3 options for sorting:
 
 * default: no sorting, so it's the fastest mode.
 * Canonical: [(RFC 7049 Section 3.9)](https://tools.ietf.org/html/rfc7049#section-3.9) uses length-first map key ordering.
 * CTAP2Canonical: [(CTAP2 Canonical CBOR)](https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#ctap2-canonical-cbor-encoding-form) uses bytewise lexicographic order for sorting keys.
 
-CTAP2 Canonical CBOR encoding is used by [CTAP](https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html) and [WebAuthn](https://www.w3.org/TR/webauthn/) in [FIDO2](https://fidoalliance.org/fido2/) framework.
+Encoder has 4 options for encoding float-point data:
+
+* default: no conversion, so it's the fastest mode.
+* ShortestFloat16: uses float16 as the shortest form that preserves value.
+* ShortestFloat32: uses float32 as the shortest form that preserves value.
+* ShortestFloat64: uses float64 as the shortest form (this can needlessly increase size of CBOR encoded data).
 
-All three encoding modes in this library use smallest form of CBOR integer that preserves data.  New encoding mode(s) will be added to do the same for floating point numbers in [milestone v2.0](https://github.com/fxamacker/cbor/milestone/3).
+For integer data types in Go, the encoder always uses the smallest form of CBOR integer that preserves data.  
 
 Decoder checks for all required well-formedness errors described in the latest RFC 7049bis, including all "subkinds" of syntax errors and too little data.
 
diff --git a/decode.go b/decode.go
index 7e45046..b4dde44 100644
--- a/decode.go
+++ b/decode.go
@@ -13,6 +13,8 @@ import (
 	"strings"
 	"time"
 	"unicode/utf8"
+
+	"github.com/cbor-go/float16"
 )
 
 // Unmarshal parses the CBOR-encoded data and stores the result in the value
@@ -267,7 +269,7 @@ func (d *decodeState) parseToValue(v reflect.Value, isUnmarshaler bool) error {
 		case 22, 23:
 			return fillNil(t, v)
 		case 25:
-			f := uint16ToFloat64(uint16(val))
+			f := float64(float16.Frombits(uint16(val)).Float32())
 			return fillFloat(t, f, v)
 		case 26:
 			f := float64(math.Float32frombits(uint32(val)))
@@ -366,7 +368,7 @@ func (d *decodeState) parse() (interface{}, error) {
 		case 22, 23:
 			return nil, nil
 		case 25:
-			f := uint16ToFloat64(uint16(val))
+			f := float64(float16.Frombits(uint16(val)).Float32())
 			return f, nil
 		case 26:
 			f := float64(math.Float32frombits(uint32(val)))
@@ -990,47 +992,6 @@ func fillTextString(t cborType, val []byte, v reflect.Value) error {
 	return &UnmarshalTypeError{Value: t.String(), Type: v.Type()}
 }
 
-func uint16ToFloat64(num uint16) float64 {
-	return float64(math.Float32frombits(f16bitsToF32bits(num)))
-}
-
-// f16bitsToF32bits returns uint32 (float32 bits) converted from specified uint16.
-func f16bitsToF32bits(in uint16) uint32 {
-	// f16bitsToF32bits was contributed to fxamacker/cbor project by
-	// Montgomery Edwards⁴⁴⁸ (github.com/x448) on Dec 24, 2019.
-	// All 65536 possible float16 to float32 conversions were verified to be correct.
-
-	sign := uint32(in&0x8000) << 16 // sign for 32-bit
-	exp := uint32(in&0x7c00) >> 10  // exponenent for 16-bit
-	coef := uint32(in&0x03ff) << 13 // significand for 32-bit
-
-	if exp == 0x1f {
-		if coef == 0 {
-			// infinity
-			return sign | 0x7f800000 | coef
-		}
-		// NaN
-		return sign | 0x7fc00000 | coef
-	}
-
-	if exp == 0 {
-		if coef == 0 {
-			// zero
-			return sign
-		}
-
-		// normalize subnormal numbers
-		exp++
-		for coef&0x7f800000 == 0 {
-			coef <<= 1
-			exp--
-		}
-		coef &= 0x007fffff
-	}
-
-	return sign | ((exp + (0x7f - 0xf)) << 23) | coef
-}
-
 func isImmutableKind(k reflect.Kind) bool {
 	switch k {
 	case reflect.Bool,
diff --git a/encode.go b/encode.go
index 5c61f08..2e3a556 100644
--- a/encode.go
+++ b/encode.go
@@ -14,6 +14,8 @@ import (
 	"strconv"
 	"sync"
 	"time"
+
+	"github.com/cbor-go/float16"
 )
 
 // Marshal returns the CBOR encoding of v.
@@ -146,11 +148,49 @@ func (sm SortMode) valid() bool {
 	return sm < maxSortMode
 }
 
+// ShortestFloatMode specifies which floating-point format should
+// be used as the shortest possible format for CBOR encoding of
+// floating-point values.
+type ShortestFloatMode int
+
+const (
+	// ShortestFloatNone makes float values encode without any conversion.
+	// This is the default for ShortestFloatMode in v1.
+	// E.g. a float32 in Go will encode to CBOR float32.  And
+	// a float64 in Go will encode to CBOR float64.
+	ShortestFloatNone ShortestFloatMode = iota
+
+	// ShortestFloat16 specifies float16 as the shortest form that preserves value.
+	// E.g. if float64 can convert to float32 while perserving value, then
+	// encoding will also try to convert float32 to float16.  So a float64 might
+	// encode as CBOR float64, float32 or float16 depending on the value.
+	ShortestFloat16
+
+	// ShortestFloat32 specifies float32 as the shortest form that preserves value.
+	// E.g. if float64 can convert to float32 while perserving value, then
+	// it will encode as CBOR float32 without any further conversion attempts.
+	ShortestFloat32
+
+	// ShortestFloat64 specifies float64 as the shortest form that preserves value.
+	// Both float32 and float64 in Go will encode to CBOR float64.
+	// WARNING: this option can needlessly increase size of CBOR encoded data.
+	ShortestFloat64
+
+	maxShortestFloat
+)
+
+func (sfm ShortestFloatMode) valid() bool {
+	return sfm < maxShortestFloat
+}
+
 // EncOptions specifies encoding options.
 type EncOptions struct {
 	// Sort specifies sorting order.
 	Sort SortMode
 
+	// ShortestFloat specifies the shortest floating-point encoding that preserves the value being encoded.
+	ShortestFloat ShortestFloatMode
+
 	// Canonical causes map and struct to be encoded in a predictable sequence
 	// of bytes by sorting map keys or struct fields according to canonical rules:
 	//     - If two keys have different lengths, the shorter one sorts earlier;
@@ -219,6 +259,9 @@ func (e *encodeState) marshal(v interface{}, opts EncOptions) error {
 		}
 		opts.Sort = SortCTAP2
 	}
+	if !opts.ShortestFloat.valid() {
+		return errors.New("cbor: invalid ShortestFloatMode " + strconv.Itoa(int(opts.ShortestFloat)))
+	}
 	_, err := encode(e, reflect.ValueOf(v), opts)
 	return err
 }
@@ -279,17 +322,30 @@ func encodeFloat(e *encodeState, v reflect.Value, opts EncOptions) (int, error)
 	if math.IsInf(f64, -1) {
 		return e.Write(cborNegativeInfinity)
 	}
-	if v.Kind() == reflect.Float32 {
-		f32 := float32(f64)
-		e.scratch[0] = byte(cborTypePrimitives) | byte(26)
-		binary.BigEndian.PutUint32(e.scratch[1:], math.Float32bits(f32))
-		e.Write(e.scratch[:5])
-		return 5, nil
+
+	fopt := opts.ShortestFloat
+	if fopt == ShortestFloat64 || (v.Kind() == reflect.Float64 && (fopt == ShortestFloatNone || overflowFloat32(f64))) {
+		// Encode float64
+		e.scratch[0] = byte(cborTypePrimitives) | byte(27)
+		binary.BigEndian.PutUint64(e.scratch[1:], math.Float64bits(f64))
+		return e.Write(e.scratch[:9])
+	}
+
+	f32 := float32(f64)
+	if fopt == ShortestFloat16 {
+		f16 := float16.Fromfloat32(f32)
+		if f16.Float32() == f32 {
+			// Encode float16
+			e.scratch[0] = byte(cborTypePrimitives) | byte(25)
+			binary.BigEndian.PutUint16(e.scratch[1:], uint16(f16))
+			return e.Write(e.scratch[:3])
+		}
 	}
-	e.scratch[0] = byte(cborTypePrimitives) | byte(27)
-	binary.BigEndian.PutUint64(e.scratch[1:], math.Float64bits(f64))
-	e.Write(e.scratch[:9])
-	return 9, nil
+
+	// Encode float32
+	e.scratch[0] = byte(cborTypePrimitives) | byte(26)
+	binary.BigEndian.PutUint32(e.scratch[1:], math.Float32bits(f32))
+	return e.Write(e.scratch[:5])
 }
 
 func encodeByteString(e *encodeState, v reflect.Value, opts EncOptions) (int, error) {
@@ -756,3 +812,8 @@ func isEmptyValue(v reflect.Value) bool {
 	}
 	return false
 }
+
+func overflowFloat32(f64 float64) bool {
+	f32 := float32(f64)
+	return float64(f32) != f64
+}
diff --git a/go.mod b/go.mod
index 13cd4a3..2bfd762 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,5 @@
 module github.com/fxamacker/cbor
 
 go 1.12
+
+require github.com/cbor-go/float16 v0.6.0
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..d6240ea
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,2 @@
+github.com/cbor-go/float16 v0.6.0 h1:OQZmSTfRgTQN31vtvXopmB0Qhyo70r/9DV3mQ1Nj9Ls=
+github.com/cbor-go/float16 v0.6.0/go.mod h1:UXY4CbDyuvGHOae3o7TKoqWJn7OrSrXAkDnpi6QFjTg=


Test patch:
diff --git a/bench_test.go b/bench_test.go
index 2ebae41..c8a47dc 100644
--- a/bench_test.go
+++ b/bench_test.go
@@ -558,6 +558,20 @@ func BenchmarkMarshalSenML(b *testing.B) {
 	}
 }
 
+func BenchmarkMarshalSenMLShortestFloat16(b *testing.B) {
+	// Data from https://tools.ietf.org/html/rfc8428#section-6
+	cborData := hexDecode("87a721781b75726e3a6465763a6f773a3130653230373361303130383030363a22fb41d303a15b00106223614120050067766f6c7461676501615602fb405e066666666666a3006763757272656e74062402fb3ff3333333333333a3006763757272656e74062302fb3ff4cccccccccccda3006763757272656e74062202fb3ff6666666666666a3006763757272656e74062102f93e00a3006763757272656e74062002fb3ff999999999999aa3006763757272656e74060002fb3ffb333333333333")
+	var v []SenMLRecord
+	if err := cbor.Unmarshal(cborData, &v); err != nil {
+		b.Fatal("Unmarshal:", err)
+	}
+	for i := 0; i < b.N; i++ {
+		if _, err := cbor.Marshal(v, cbor.EncOptions{ShortestFloat: cbor.ShortestFloat16}); err != nil {
+			b.Fatal("Unmarshal:", err)
+		}
+	}
+}
+
 func BenchmarkUnmarshalWebAuthn(b *testing.B) {
 	// Data generated from Yubico security key
 	cborData := hexDecode("a363666d74686669646f2d7532666761747453746d74a26373696758483046022100e7ab373cfbd99fcd55fd59b0f6f17fef5b77a20ddec3db7f7e4d55174e366236022100828336b4822125fb56541fb14a8a273876acd339395ec2dad95cf41c1dd2a9ae637835638159024e3082024a30820132a0030201020204124a72fe300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a302c312a302806035504030c2159756269636f205532462045452053657269616c203234393431343937323135383059301306072a8648ce3d020106082a8648ce3d030107034200043d8b1bbd2fcbf6086e107471601468484153c1c6d3b4b68a5e855e6e40757ee22bcd8988bf3befd7cdf21cb0bf5d7a150d844afe98103c6c6607d9faae287c02a33b3039302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e313013060b2b0601040182e51c020101040403020520300d06092a864886f70d01010b05000382010100a14f1eea0076f6b8476a10a2be72e60d0271bb465b2dfbfc7c1bd12d351989917032631d795d097fa30a26a325634e85721bc2d01a86303f6bc075e5997319e122148b0496eec8d1f4f94cf4110de626c289443d1f0f5bbb239ca13e81d1d5aa9df5af8e36126475bfc23af06283157252762ff68879bcf0ef578d55d67f951b4f32b63c8aea5b0f99c67d7d814a7ff5a6f52df83e894a3a5d9c8b82e7f8bc8daf4c80175ff8972fda79333ec465d806eacc948f1bab22045a95558a48c20226dac003d41fbc9e05ea28a6bb5e10a49de060a0a4f6a2676a34d68c4abe8c61874355b9027e828ca9e064b002d62e8d8cf0744921753d35e3c87c5d5779453e7768617574684461746158c449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d976341000000000000000000000000000000000000000000408903fd7dfd2c9770e98cae0123b13a2c27828a106349bc6277140e7290b7e9eb7976aa3c04ed347027caf7da3a2fa76304751c02208acfc4e7fc6c7ebbc375c8a5010203262001215820ad7f7992c335b90d882b2802061b97a4fabca7e2ee3e7a51e728b8055e4eb9c7225820e0966ba7005987fece6f0e0e13447aa98cec248e4000a594b01b74c1cb1d40b3")
diff --git a/encode_test.go b/encode_test.go
index b5f3a3d..f18225b 100644
--- a/encode_test.go
+++ b/encode_test.go
@@ -1937,3 +1937,252 @@ func TestNewTypeWithBuiltinUnderlyingType(t *testing.T) {
 		})
 	}
 }
+
+func TestShortestFloat16(t *testing.T) {
+	testCases := []struct {
+		name         string
+		f64          float64
+		wantCborData []byte
+	}{
+		// Data from RFC 7049 appendix A
+		{"Shrink to float16", 0.0, hexDecode("f90000")},
+		{"Shrink to float16", 1.0, hexDecode("f93c00")},
+		{"Shrink to float16", 1.5, hexDecode("f93e00")},
+		{"Shrink to float16", 65504.0, hexDecode("f97bff")},
+		{"Shrink to float16", 5.960464477539063e-08, hexDecode("f90001")},
+		{"Shrink to float16", 6.103515625e-05, hexDecode("f90400")},
+		{"Shrink to float16", -4.0, hexDecode("f9c400")},
+		// Data from https://en.wikipedia.org/wiki/Half-precision_floating-point_format
+		{"Shrink to float16", 0.333251953125, hexDecode("f93555")},
+		// Data from 7049bis 4.2.1 and 5.5
+		{"Shrink to float16", 5.5, hexDecode("f94580")},
+		// Data from RFC 7049 appendix A
+		{"Shrink to float32", 100000.0, hexDecode("fa47c35000")},
+		{"Shrink to float32", 3.4028234663852886e+38, hexDecode("fa7f7fffff")},
+		// Data from 7049bis 4.2.1 and 5.5
+		{"Shrink to float32", 5555.5, hexDecode("fa45ad9c00")},
+		{"Shrink to float32", 1000000.5, hexDecode("fa49742408")},
+		// Data from RFC 7049 appendix A
+		{"Shrink to float64", 1.0e+300, hexDecode("fb7e37e43c8800759c")},
+	}
+	opts := cbor.EncOptions{ShortestFloat: cbor.ShortestFloat16}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			b, err := cbor.Marshal(tc.f64, opts)
+			if err != nil {
+				t.Errorf("Marshal(%v) returns error %s", tc.f64, err)
+			} else if !bytes.Equal(b, tc.wantCborData) {
+				t.Errorf("Marshal(%v) = 0x%0x, want 0x%0x", tc.f64, b, tc.wantCborData)
+			}
+			var f64 float64
+			if err = cbor.Unmarshal(b, &f64); err != nil {
+				t.Errorf("Unmarshal(0x%0x) returns error %s", b, err)
+			} else if f64 != tc.f64 {
+				t.Errorf("Unmarshal(0x%0x) = %f, want %f", b, f64, tc.f64)
+			}
+		})
+	}
+}
+
+func TestShortestFloat32(t *testing.T) {
+	testCases := []struct {
+		name         string
+		f64          float64
+		wantCborData []byte
+	}{
+		// Data from RFC 7049 appendix A
+		{"Shrink to float32", 0.0, hexDecode("fa00000000")},
+		{"Shrink to float32", 1.0, hexDecode("fa3f800000")},
+		{"Shrink to float32", 1.5, hexDecode("fa3fc00000")},
+		{"Shrink to float32", 65504.0, hexDecode("fa477fe000")},
+		{"Shrink to float32", 5.960464477539063e-08, hexDecode("fa33800000")},
+		{"Shrink to float32", 6.103515625e-05, hexDecode("fa38800000")},
+		{"Shrink to float32", -4.0, hexDecode("fac0800000")},
+		// Data from https://en.wikipedia.org/wiki/Half-precision_floating-point_format
+		{"Shrink to float32", 0.333251953125, hexDecode("fa3eaaa000")},
+		// Data from 7049bis 4.2.1 and 5.5
+		{"Shrink to float32", 5.5, hexDecode("fa40b00000")},
+		// Data from RFC 7049 appendix A
+		{"Shrink to float32", 100000.0, hexDecode("fa47c35000")},
+		{"Shrink to float32", 3.4028234663852886e+38, hexDecode("fa7f7fffff")},
+		// Data from 7049bis 4.2.1 and 5.5
+		{"Shrink to float32", 5555.5, hexDecode("fa45ad9c00")},
+		{"Shrink to float32", 1000000.5, hexDecode("fa49742408")},
+		// Data from RFC 7049 appendix A
+		{"Shrink to float64", 1.0e+300, hexDecode("fb7e37e43c8800759c")},
+	}
+	opts := cbor.EncOptions{ShortestFloat: cbor.ShortestFloat32}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			b, err := cbor.Marshal(tc.f64, opts)
+			if err != nil {
+				t.Errorf("Marshal(%v) returns error %s", tc.f64, err)
+			} else if !bytes.Equal(b, tc.wantCborData) {
+				t.Errorf("Marshal(%v) = 0x%0x, want 0x%0x", tc.f64, b, tc.wantCborData)
+			}
+			var f64 float64
+			if err = cbor.Unmarshal(b, &f64); err != nil {
+				t.Errorf("Unmarshal(0x%0x) returns error %s", b, err)
+			} else if f64 != tc.f64 {
+				t.Errorf("Unmarshal(0x%0x) = %f, want %f", b, f64, tc.f64)
+			}
+		})
+	}
+}
+
+func TestShortestFloat64(t *testing.T) {
+	testCases := []struct {
+		name         string
+		f64          float64
+		wantCborData []byte
+	}{
+		// Data from RFC 7049 appendix A
+		{"Shrink to float64", 0.0, hexDecode("fb0000000000000000")},
+		{"Shrink to float64", 1.0, hexDecode("fb3ff0000000000000")},
+		{"Shrink to float64", 1.5, hexDecode("fb3ff8000000000000")},
+		{"Shrink to float64", 65504.0, hexDecode("fb40effc0000000000")},
+		{"Shrink to float64", 5.960464477539063e-08, hexDecode("fb3e70000000000000")},
+		{"Shrink to float64", 6.103515625e-05, hexDecode("fb3f10000000000000")},
+		{"Shrink to float64", -4.0, hexDecode("fbc010000000000000")},
+		// Data from https://en.wikipedia.org/wiki/Half-precision_floating-point_format
+		{"Shrink to float64", 0.333251953125, hexDecode("fb3fd5540000000000")},
+		// Data from 7049bis 4.2.1 and 5.5
+		{"Shrink to float64", 5.5, hexDecode("fb4016000000000000")},
+		// Data from RFC 7049 appendix A
+		{"Shrink to float64", 100000.0, hexDecode("fb40f86a0000000000")},
+		{"Shrink to float64", 3.4028234663852886e+38, hexDecode("fb47efffffe0000000")},
+		// Data from 7049bis 4.2.1 and 5.5
+		{"Shrink to float64", 5555.5, hexDecode("fb40b5b38000000000")},
+		{"Shrink to float64", 1000000.5, hexDecode("fb412e848100000000")},
+		// Data from RFC 7049 appendix A
+		{"Shrink to float64", 1.0e+300, hexDecode("fb7e37e43c8800759c")},
+	}
+	opts := cbor.EncOptions{ShortestFloat: cbor.ShortestFloat64}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			b, err := cbor.Marshal(tc.f64, opts)
+			if err != nil {
+				t.Errorf("Marshal(%v) returns error %s", tc.f64, err)
+			} else if !bytes.Equal(b, tc.wantCborData) {
+				t.Errorf("Marshal(%v) = 0x%0x, want 0x%0x", tc.f64, b, tc.wantCborData)
+			}
+			var f64 float64
+			if err = cbor.Unmarshal(b, &f64); err != nil {
+				t.Errorf("Unmarshal(0x%0x) returns error %s", b, err)
+			} else if f64 != tc.f64 {
+				t.Errorf("Unmarshal(0x%0x) = %f, want %f", b, f64, tc.f64)
+			}
+		})
+	}
+}
+
+func TestShortestFloatNone(t *testing.T) {
+	testCases := []struct {
+		name         string
+		f            interface{}
+		wantCborData []byte
+	}{
+		// Data from RFC 7049 appendix A
+		{"float32", float32(0.0), hexDecode("fa00000000")},
+		{"float64", float64(0.0), hexDecode("fb0000000000000000")},
+		{"float32", float32(1.0), hexDecode("fa3f800000")},
+		{"float64", float64(1.0), hexDecode("fb3ff0000000000000")},
+		{"float32", float32(1.5), hexDecode("fa3fc00000")},
+		{"float64", float64(1.5), hexDecode("fb3ff8000000000000")},
+		{"float32", float32(65504.0), hexDecode("fa477fe000")},
+		{"float64", float64(65504.0), hexDecode("fb40effc0000000000")},
+		{"float32", float32(5.960464477539063e-08), hexDecode("fa33800000")},
+		{"float64", float64(5.960464477539063e-08), hexDecode("fb3e70000000000000")},
+		{"float32", float32(6.103515625e-05), hexDecode("fa38800000")},
+		{"float64", float64(6.103515625e-05), hexDecode("fb3f10000000000000")},
+		{"float32", float32(-4.0), hexDecode("fac0800000")},
+		{"float64", float64(-4.0), hexDecode("fbc010000000000000")},
+		// Data from https://en.wikipedia.org/wiki/Half-precision_floating-point_format
+		{"float32", float32(0.333251953125), hexDecode("fa3eaaa000")},
+		{"float64", float64(0.333251953125), hexDecode("fb3fd5540000000000")},
+		// Data from 7049bis 4.2.1 and 5.5
+		{"float32", float32(5.5), hexDecode("fa40b00000")},
+		{"float64", float64(5.5), hexDecode("fb4016000000000000")},
+		// Data from RFC 7049 appendix A
+		{"float32", float32(100000.0), hexDecode("fa47c35000")},
+		{"float64", float64(100000.0), hexDecode("fb40f86a0000000000")},
+		{"float32", float32(3.4028234663852886e+38), hexDecode("fa7f7fffff")},
+		{"float64", float64(3.4028234663852886e+38), hexDecode("fb47efffffe0000000")},
+		// Data from 7049bis 4.2.1 and 5.5
+		{"float32", float32(5555.5), hexDecode("fa45ad9c00")},
+		{"float64", float64(5555.5), hexDecode("fb40b5b38000000000")},
+		{"float32", float32(1000000.5), hexDecode("fa49742408")},
+		{"float64", float64(1000000.5), hexDecode("fb412e848100000000")},
+		{"float64", float64(1.0e+300), hexDecode("fb7e37e43c8800759c")},
+	}
+	opts := cbor.EncOptions{ShortestFloat: cbor.ShortestFloatNone}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			b, err := cbor.Marshal(tc.f, opts)
+			if err != nil {
+				t.Errorf("Marshal(%v) returns error %s", tc.f, err)
+			} else if !bytes.Equal(b, tc.wantCborData) {
+				t.Errorf("Marshal(%v) = 0x%0x, want 0x%0x", tc.f, b, tc.wantCborData)
+			}
+			if reflect.ValueOf(tc.f).Kind() == reflect.Float32 {
+				var f32 float32
+				if err = cbor.Unmarshal(b, &f32); err != nil {
+					t.Errorf("Unmarshal(0x%0x) returns error %s", b, err)
+				} else if f32 != tc.f {
+					t.Errorf("Unmarshal(0x%0x) = %f, want %f", b, f32, tc.f)
+				}
+			} else {
+				var f64 float64
+				if err = cbor.Unmarshal(b, &f64); err != nil {
+					t.Errorf("Unmarshal(0x%0x) returns error %s", b, err)
+				} else if f64 != tc.f {
+					t.Errorf("Unmarshal(0x%0x) = %f, want %f", b, f64, tc.f)
+				}
+			}
+		})
+	}
+}
+
+func TestInvalidShortestFloat(t *testing.T) {
+	var i int
+	wantErrorMsg := "cbor: invalid ShortestFloatMode 100"
+	if _, err := cbor.Marshal(i, cbor.EncOptions{ShortestFloat: cbor.ShortestFloatMode(100)}); err == nil {
+		t.Errorf("Marshal() doens't return error")
+	} else if err.Error() != wantErrorMsg {
+		t.Errorf("Marshal() returns error %s, want %s", err, wantErrorMsg)
+	}
+}
+
+func TestMarshalSenML(t *testing.T) {
+	// Data from https://tools.ietf.org/html/rfc8428#section-6
+	// Data contains 13 floating-point numbers.
+	cborData := hexDecode("87a721781b75726e3a6465763a6f773a3130653230373361303130383030363a22fb41d303a15b00106223614120050067766f6c7461676501615602fb405e066666666666a3006763757272656e74062402fb3ff3333333333333a3006763757272656e74062302fb3ff4cccccccccccda3006763757272656e74062202fb3ff6666666666666a3006763757272656e74062102f93e00a3006763757272656e74062002fb3ff999999999999aa3006763757272656e74060002fb3ffb333333333333")
+	testCases := []struct {
+		name string
+		opts cbor.EncOptions
+	}{
+		{"EncOptions ShortestFloatNone", cbor.EncOptions{}},
+		{"EncOptions ShortestFloat16", cbor.EncOptions{ShortestFloat: cbor.ShortestFloat16}},
+		{"EncOptions ShortestFloat32", cbor.EncOptions{ShortestFloat: cbor.ShortestFloat32}},
+		{"EncOptions ShortestFloat64", cbor.EncOptions{ShortestFloat: cbor.ShortestFloat64}},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			var v []SenMLRecord
+			if err := cbor.Unmarshal(cborData, &v); err != nil {
+				t.Errorf("Marshal() returns error %s", err)
+			}
+			b, err := cbor.Marshal(v, tc.opts)
+			if err != nil {
+				t.Errorf("Unmarshal() returns error %s ", err)
+			}
+			var v2 []SenMLRecord
+			if err := cbor.Unmarshal(b, &v2); err != nil {
+				t.Errorf("Marshal() returns error %s", err)
+			}
+			if !reflect.DeepEqual(v, v2) {
+				t.Errorf("SenML round-trip failed: v1 %+v, v2 %+v", v, v2)
+			}
+		})
+	}
+}