Repository: rs/zerolog (go)

Issue:
Floating Point Precision
I can understand why  `-1`  was used [here](https://github.com/rs/zerolog/blob/9070d49a1a4154c819e9c5ae6c923f3cc916d959/internal/json/types.go#LL314C44-L314C44), but it can cause some pain when loading the message, say in elasticsearch with the value `0` instead of `0.00` for example.

For me, it creates a field of type `long` instead of what I really want it to be, a `float` and I'm sure there may be other specific cases.

I propose adding a `FloatingPointPrecission` variable somewhere around [here](https://github.com/rs/zerolog/blob/9070d49a1a4154c819e9c5ae6c923f3cc916d959/globals.go#L110) with a default value of `-1`, then everyone is happy :)


Pull request description:
Allow setting floating point precision in JSON
Fixes #557.

Patch:
diff --git a/README.md b/README.md
index bc8dd61..1306a6c 100644
--- a/README.md
+++ b/README.md
@@ -642,6 +642,10 @@ Some settings can be changed and will be applied to all loggers:
 * `zerolog.DurationFieldUnit`: Can be set to customize the unit for time.Duration type fields added by `Dur` (default: `time.Millisecond`).
 * `zerolog.DurationFieldInteger`: If set to `true`, `Dur` fields are formatted as integers instead of floats (default: `false`). 
 * `zerolog.ErrorHandler`: Called whenever zerolog fails to write an event on its output. If not set, an error is printed on the stderr. This handler must be thread safe and non-blocking.
+* `zerolog.FloatingPointPrecision`: If set to a value other than -1, controls the number
+of digits when formatting float numbers in JSON. See
+[strconv.FormatFloat](https://pkg.go.dev/strconv#FormatFloat)
+for more details.
 
 ## Field Types
 
diff --git a/array.go b/array.go
index 99612ee..ba35b28 100644
--- a/array.go
+++ b/array.go
@@ -183,13 +183,13 @@ func (a *Array) Uint64(i uint64) *Array {
 
 // Float32 appends f as a float32 to the array.
 func (a *Array) Float32(f float32) *Array {
-	a.buf = enc.AppendFloat32(enc.AppendArrayDelim(a.buf), f)
+	a.buf = enc.AppendFloat32(enc.AppendArrayDelim(a.buf), f, FloatingPointPrecision)
 	return a
 }
 
 // Float64 appends f as a float64 to the array.
 func (a *Array) Float64(f float64) *Array {
-	a.buf = enc.AppendFloat64(enc.AppendArrayDelim(a.buf), f)
+	a.buf = enc.AppendFloat64(enc.AppendArrayDelim(a.buf), f, FloatingPointPrecision)
 	return a
 }
 
@@ -201,7 +201,7 @@ func (a *Array) Time(t time.Time) *Array {
 
 // Dur appends d to the array.
 func (a *Array) Dur(d time.Duration) *Array {
-	a.buf = enc.AppendDuration(enc.AppendArrayDelim(a.buf), d, DurationFieldUnit, DurationFieldInteger)
+	a.buf = enc.AppendDuration(enc.AppendArrayDelim(a.buf), d, DurationFieldUnit, DurationFieldInteger, FloatingPointPrecision)
 	return a
 }
 
diff --git a/context.go b/context.go
index 05e4baf..ff9a3ae 100644
--- a/context.go
+++ b/context.go
@@ -325,25 +325,25 @@ func (c Context) Uints64(key string, i []uint64) Context {
 
 // Float32 adds the field key with f as a float32 to the logger context.
 func (c Context) Float32(key string, f float32) Context {
-	c.l.context = enc.AppendFloat32(enc.AppendKey(c.l.context, key), f)
+	c.l.context = enc.AppendFloat32(enc.AppendKey(c.l.context, key), f, FloatingPointPrecision)
 	return c
 }
 
 // Floats32 adds the field key with f as a []float32 to the logger context.
 func (c Context) Floats32(key string, f []float32) Context {
-	c.l.context = enc.AppendFloats32(enc.AppendKey(c.l.context, key), f)
+	c.l.context = enc.AppendFloats32(enc.AppendKey(c.l.context, key), f, FloatingPointPrecision)
 	return c
 }
 
 // Float64 adds the field key with f as a float64 to the logger context.
 func (c Context) Float64(key string, f float64) Context {
-	c.l.context = enc.AppendFloat64(enc.AppendKey(c.l.context, key), f)
+	c.l.context = enc.AppendFloat64(enc.AppendKey(c.l.context, key), f, FloatingPointPrecision)
 	return c
 }
 
 // Floats64 adds the field key with f as a []float64 to the logger context.
 func (c Context) Floats64(key string, f []float64) Context {
-	c.l.context = enc.AppendFloats64(enc.AppendKey(c.l.context, key), f)
+	c.l.context = enc.AppendFloats64(enc.AppendKey(c.l.context, key), f, FloatingPointPrecision)
 	return c
 }
 
@@ -379,13 +379,13 @@ func (c Context) Times(key string, t []time.Time) Context {
 
 // Dur adds the fields key with d divided by unit and stored as a float.
 func (c Context) Dur(key string, d time.Duration) Context {
-	c.l.context = enc.AppendDuration(enc.AppendKey(c.l.context, key), d, DurationFieldUnit, DurationFieldInteger)
+	c.l.context = enc.AppendDuration(enc.AppendKey(c.l.context, key), d, DurationFieldUnit, DurationFieldInteger, FloatingPointPrecision)
 	return c
 }
 
 // Durs adds the fields key with d divided by unit and stored as a float.
 func (c Context) Durs(key string, d []time.Duration) Context {
-	c.l.context = enc.AppendDurations(enc.AppendKey(c.l.context, key), d, DurationFieldUnit, DurationFieldInteger)
+	c.l.context = enc.AppendDurations(enc.AppendKey(c.l.context, key), d, DurationFieldUnit, DurationFieldInteger, FloatingPointPrecision)
 	return c
 }
 
diff --git a/encoder.go b/encoder.go
index 09b24e8..4dbaf38 100644
--- a/encoder.go
+++ b/encoder.go
@@ -13,13 +13,13 @@ type encoder interface {
 	AppendBool(dst []byte, val bool) []byte
 	AppendBools(dst []byte, vals []bool) []byte
 	AppendBytes(dst, s []byte) []byte
-	AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool) []byte
-	AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool) []byte
+	AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool, precision int) []byte
+	AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool, precision int) []byte
 	AppendEndMarker(dst []byte) []byte
-	AppendFloat32(dst []byte, val float32) []byte
-	AppendFloat64(dst []byte, val float64) []byte
-	AppendFloats32(dst []byte, vals []float32) []byte
-	AppendFloats64(dst []byte, vals []float64) []byte
+	AppendFloat32(dst []byte, val float32, precision int) []byte
+	AppendFloat64(dst []byte, val float64, precision int) []byte
+	AppendFloats32(dst []byte, vals []float32, precision int) []byte
+	AppendFloats64(dst []byte, vals []float64, precision int) []byte
 	AppendHex(dst, s []byte) []byte
 	AppendIPAddr(dst []byte, ip net.IP) []byte
 	AppendIPPrefix(dst []byte, pfx net.IPNet) []byte
diff --git a/event.go b/event.go
index 5c949f8..56de606 100644
--- a/event.go
+++ b/event.go
@@ -644,7 +644,7 @@ func (e *Event) Float32(key string, f float32) *Event {
 	if e == nil {
 		return e
 	}
-	e.buf = enc.AppendFloat32(enc.AppendKey(e.buf, key), f)
+	e.buf = enc.AppendFloat32(enc.AppendKey(e.buf, key), f, FloatingPointPrecision)
 	return e
 }
 
@@ -653,7 +653,7 @@ func (e *Event) Floats32(key string, f []float32) *Event {
 	if e == nil {
 		return e
 	}
-	e.buf = enc.AppendFloats32(enc.AppendKey(e.buf, key), f)
+	e.buf = enc.AppendFloats32(enc.AppendKey(e.buf, key), f, FloatingPointPrecision)
 	return e
 }
 
@@ -662,7 +662,7 @@ func (e *Event) Float64(key string, f float64) *Event {
 	if e == nil {
 		return e
 	}
-	e.buf = enc.AppendFloat64(enc.AppendKey(e.buf, key), f)
+	e.buf = enc.AppendFloat64(enc.AppendKey(e.buf, key), f, FloatingPointPrecision)
 	return e
 }
 
@@ -671,7 +671,7 @@ func (e *Event) Floats64(key string, f []float64) *Event {
 	if e == nil {
 		return e
 	}
-	e.buf = enc.AppendFloats64(enc.AppendKey(e.buf, key), f)
+	e.buf = enc.AppendFloats64(enc.AppendKey(e.buf, key), f, FloatingPointPrecision)
 	return e
 }
 
@@ -713,7 +713,7 @@ func (e *Event) Dur(key string, d time.Duration) *Event {
 	if e == nil {
 		return e
 	}
-	e.buf = enc.AppendDuration(enc.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger)
+	e.buf = enc.AppendDuration(enc.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger, FloatingPointPrecision)
 	return e
 }
 
@@ -724,7 +724,7 @@ func (e *Event) Durs(key string, d []time.Duration) *Event {
 	if e == nil {
 		return e
 	}
-	e.buf = enc.AppendDurations(enc.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger)
+	e.buf = enc.AppendDurations(enc.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger, FloatingPointPrecision)
 	return e
 }
 
@@ -739,7 +739,7 @@ func (e *Event) TimeDiff(key string, t time.Time, start time.Time) *Event {
 	if t.After(start) {
 		d = t.Sub(start)
 	}
-	e.buf = enc.AppendDuration(enc.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger)
+	e.buf = enc.AppendDuration(enc.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger, FloatingPointPrecision)
 	return e
 }
 
diff --git a/fields.go b/fields.go
index 23606dd..99f5271 100644
--- a/fields.go
+++ b/fields.go
@@ -139,13 +139,13 @@ func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte {
 		case uint64:
 			dst = enc.AppendUint64(dst, val)
 		case float32:
-			dst = enc.AppendFloat32(dst, val)
+			dst = enc.AppendFloat32(dst, val, FloatingPointPrecision)
 		case float64:
-			dst = enc.AppendFloat64(dst, val)
+			dst = enc.AppendFloat64(dst, val, FloatingPointPrecision)
 		case time.Time:
 			dst = enc.AppendTime(dst, val, TimeFieldFormat)
 		case time.Duration:
-			dst = enc.AppendDuration(dst, val, DurationFieldUnit, DurationFieldInteger)
+			dst = enc.AppendDuration(dst, val, DurationFieldUnit, DurationFieldInteger, FloatingPointPrecision)
 		case *string:
 			if val != nil {
 				dst = enc.AppendString(dst, *val)
@@ -220,13 +220,13 @@ func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte {
 			}
 		case *float32:
 			if val != nil {
-				dst = enc.AppendFloat32(dst, *val)
+				dst = enc.AppendFloat32(dst, *val, FloatingPointPrecision)
 			} else {
 				dst = enc.AppendNil(dst)
 			}
 		case *float64:
 			if val != nil {
-				dst = enc.AppendFloat64(dst, *val)
+				dst = enc.AppendFloat64(dst, *val, FloatingPointPrecision)
 			} else {
 				dst = enc.AppendNil(dst)
 			}
@@ -238,7 +238,7 @@ func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte {
 			}
 		case *time.Duration:
 			if val != nil {
-				dst = enc.AppendDuration(dst, *val, DurationFieldUnit, DurationFieldInteger)
+				dst = enc.AppendDuration(dst, *val, DurationFieldUnit, DurationFieldInteger, FloatingPointPrecision)
 			} else {
 				dst = enc.AppendNil(dst)
 			}
@@ -267,13 +267,13 @@ func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte {
 		case []uint64:
 			dst = enc.AppendUints64(dst, val)
 		case []float32:
-			dst = enc.AppendFloats32(dst, val)
+			dst = enc.AppendFloats32(dst, val, FloatingPointPrecision)
 		case []float64:
-			dst = enc.AppendFloats64(dst, val)
+			dst = enc.AppendFloats64(dst, val, FloatingPointPrecision)
 		case []time.Time:
 			dst = enc.AppendTimes(dst, val, TimeFieldFormat)
 		case []time.Duration:
-			dst = enc.AppendDurations(dst, val, DurationFieldUnit, DurationFieldInteger)
+			dst = enc.AppendDurations(dst, val, DurationFieldUnit, DurationFieldInteger, FloatingPointPrecision)
 		case nil:
 			dst = enc.AppendNil(dst)
 		case net.IP:
diff --git a/globals.go b/globals.go
index ac1d9c4..9a9be71 100644
--- a/globals.go
+++ b/globals.go
@@ -151,6 +151,11 @@ var (
 	// TriggerLevelWriterBufferReuseLimit is a limit in bytes that a buffer is dropped
 	// from the TriggerLevelWriter buffer pool if the buffer grows above the limit.
 	TriggerLevelWriterBufferReuseLimit = 64 * 1024
+
+	// FloatingPointPrecision, if set to a value other than -1, controls the number
+	// of digits when formatting float numbers in JSON. See strconv.FormatFloat for
+	// more details.
+	FloatingPointPrecision = -1
 )
 
 var (
diff --git a/internal/cbor/time.go b/internal/cbor/time.go
index d81fb12..7c0ecce 100644
--- a/internal/cbor/time.go
+++ b/internal/cbor/time.go
@@ -29,7 +29,7 @@ func (e Encoder) appendFloatTimestamp(dst []byte, t time.Time) []byte {
 	nanos := t.Nanosecond()
 	var val float64
 	val = float64(secs)*1.0 + float64(nanos)*1e-9
-	return e.AppendFloat64(dst, val)
+	return e.AppendFloat64(dst, val, -1)
 }
 
 // AppendTime encodes and adds a timestamp to the dst byte array.
@@ -64,17 +64,17 @@ func (e Encoder) AppendTimes(dst []byte, vals []time.Time, unused string) []byte
 // AppendDuration encodes and adds a duration to the dst byte array.
 // useInt field indicates whether to store the duration as seconds (integer) or
 // as seconds+nanoseconds (float).
-func (e Encoder) AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool) []byte {
+func (e Encoder) AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool, unused int) []byte {
 	if useInt {
 		return e.AppendInt64(dst, int64(d/unit))
 	}
-	return e.AppendFloat64(dst, float64(d)/float64(unit))
+	return e.AppendFloat64(dst, float64(d)/float64(unit), unused)
 }
 
 // AppendDurations encodes and adds an array of durations to the dst byte array.
 // useInt field indicates whether to store the duration as seconds (integer) or
 // as seconds+nanoseconds (float).
-func (e Encoder) AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool) []byte {
+func (e Encoder) AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool, unused int) []byte {
 	major := majorTypeArray
 	l := len(vals)
 	if l == 0 {
@@ -87,7 +87,7 @@ func (e Encoder) AppendDurations(dst []byte, vals []time.Duration, unit time.Dur
 		dst = appendCborTypePrefix(dst, major, uint64(l))
 	}
 	for _, d := range vals {
-		dst = e.AppendDuration(dst, d, unit, useInt)
+		dst = e.AppendDuration(dst, d, unit, useInt, unused)
 	}
 	return dst
 }
diff --git a/internal/cbor/types.go b/internal/cbor/types.go
index 6f53832..454d68b 100644
--- a/internal/cbor/types.go
+++ b/internal/cbor/types.go
@@ -352,7 +352,7 @@ func (e Encoder) AppendUints64(dst []byte, vals []uint64) []byte {
 }
 
 // AppendFloat32 encodes and inserts a single precision float value into the dst byte array.
-func (Encoder) AppendFloat32(dst []byte, val float32) []byte {
+func (Encoder) AppendFloat32(dst []byte, val float32, unused int) []byte {
 	switch {
 	case math.IsNaN(float64(val)):
 		return append(dst, "\xfa\x7f\xc0\x00\x00"...)
@@ -372,7 +372,7 @@ func (Encoder) AppendFloat32(dst []byte, val float32) []byte {
 }
 
 // AppendFloats32 encodes and inserts an array of single precision float value into the dst byte array.
-func (e Encoder) AppendFloats32(dst []byte, vals []float32) []byte {
+func (e Encoder) AppendFloats32(dst []byte, vals []float32, unused int) []byte {
 	major := majorTypeArray
 	l := len(vals)
 	if l == 0 {
@@ -385,13 +385,13 @@ func (e Encoder) AppendFloats32(dst []byte, vals []float32) []byte {
 		dst = appendCborTypePrefix(dst, major, uint64(l))
 	}
 	for _, v := range vals {
-		dst = e.AppendFloat32(dst, v)
+		dst = e.AppendFloat32(dst, v, unused)
 	}
 	return dst
 }
 
 // AppendFloat64 encodes and inserts a double precision float value into the dst byte array.
-func (Encoder) AppendFloat64(dst []byte, val float64) []byte {
+func (Encoder) AppendFloat64(dst []byte, val float64, unused int) []byte {
 	switch {
 	case math.IsNaN(val):
 		return append(dst, "\xfb\x7f\xf8\x00\x00\x00\x00\x00\x00"...)
@@ -412,7 +412,7 @@ func (Encoder) AppendFloat64(dst []byte, val float64) []byte {
 }
 
 // AppendFloats64 encodes and inserts an array of double precision float values into the dst byte array.
-func (e Encoder) AppendFloats64(dst []byte, vals []float64) []byte {
+func (e Encoder) AppendFloats64(dst []byte, vals []float64, unused int) []byte {
 	major := majorTypeArray
 	l := len(vals)
 	if l == 0 {
@@ -425,7 +425,7 @@ func (e Encoder) AppendFloats64(dst []byte, vals []float64) []byte {
 		dst = appendCborTypePrefix(dst, major, uint64(l))
 	}
 	for _, v := range vals {
-		dst = e.AppendFloat64(dst, v)
+		dst = e.AppendFloat64(dst, v, unused)
 	}
 	return dst
 }
diff --git a/internal/json/time.go b/internal/json/time.go
index 6a8dc91..08cbbd9 100644
--- a/internal/json/time.go
+++ b/internal/json/time.go
@@ -88,24 +88,24 @@ func appendUnixNanoTimes(dst []byte, vals []time.Time, div int64) []byte {
 
 // AppendDuration formats the input duration with the given unit & format
 // and appends the encoded string to the input byte slice.
-func (e Encoder) AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool) []byte {
+func (e Encoder) AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool, precision int) []byte {
 	if useInt {
 		return strconv.AppendInt(dst, int64(d/unit), 10)
 	}
-	return e.AppendFloat64(dst, float64(d)/float64(unit))
+	return e.AppendFloat64(dst, float64(d)/float64(unit), precision)
 }
 
 // AppendDurations formats the input durations with the given unit & format
 // and appends the encoded string list to the input byte slice.
-func (e Encoder) AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool) []byte {
+func (e Encoder) AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool, precision int) []byte {
 	if len(vals) == 0 {
 		return append(dst, '[', ']')
 	}
 	dst = append(dst, '[')
-	dst = e.AppendDuration(dst, vals[0], unit, useInt)
+	dst = e.AppendDuration(dst, vals[0], unit, useInt, precision)
 	if len(vals) > 1 {
 		for _, d := range vals[1:] {
-			dst = e.AppendDuration(append(dst, ','), d, unit, useInt)
+			dst = e.AppendDuration(append(dst, ','), d, unit, useInt, precision)
 		}
 	}
 	dst = append(dst, ']')
diff --git a/internal/json/types.go b/internal/json/types.go
index 43feaaa..7930491 100644
--- a/internal/json/types.go
+++ b/internal/json/types.go
@@ -299,7 +299,7 @@ func (Encoder) AppendUints64(dst []byte, vals []uint64) []byte {
 	return dst
 }
 
-func appendFloat(dst []byte, val float64, bitSize int) []byte {
+func appendFloat(dst []byte, val float64, bitSize, precision int) []byte {
 	// JSON does not permit NaN or Infinity. A typical JSON encoder would fail
 	// with an error, but a logging library wants the data to get through so we
 	// make a tradeoff and store those types as string.
@@ -314,13 +314,16 @@ func appendFloat(dst []byte, val float64, bitSize int) []byte {
 	// convert as if by es6 number to string conversion
 	// see also https://cs.opensource.google/go/go/+/refs/tags/go1.20.3:src/encoding/json/encode.go;l=573
 	strFmt := byte('f')
-	// Use float32 comparisons for underlying float32 value to get precise cutoffs right.
-	if abs := math.Abs(val); abs != 0 {
-		if bitSize == 64 && (abs < 1e-6 || abs >= 1e21) || bitSize == 32 && (float32(abs) < 1e-6 || float32(abs) >= 1e21) {
-			strFmt = 'e'
+	// If precision is set to a value other than -1, we always just format the float using that precision.
+	if precision == -1 {
+		// Use float32 comparisons for underlying float32 value to get precise cutoffs right.
+		if abs := math.Abs(val); abs != 0 {
+			if bitSize == 64 && (abs < 1e-6 || abs >= 1e21) || bitSize == 32 && (float32(abs) < 1e-6 || float32(abs) >= 1e21) {
+				strFmt = 'e'
+			}
 		}
 	}
-	dst = strconv.AppendFloat(dst, val, strFmt, -1, bitSize)
+	dst = strconv.AppendFloat(dst, val, strFmt, precision, bitSize)
 	if strFmt == 'e' {
 		// Clean up e-09 to e-9
 		n := len(dst)
@@ -334,21 +337,21 @@ func appendFloat(dst []byte, val float64, bitSize int) []byte {
 
 // AppendFloat32 converts the input float32 to a string and
 // appends the encoded string to the input byte slice.
-func (Encoder) AppendFloat32(dst []byte, val float32) []byte {
-	return appendFloat(dst, float64(val), 32)
+func (Encoder) AppendFloat32(dst []byte, val float32, precision int) []byte {
+	return appendFloat(dst, float64(val), 32, precision)
 }
 
 // AppendFloats32 encodes the input float32s to json and
 // appends the encoded string list to the input byte slice.
-func (Encoder) AppendFloats32(dst []byte, vals []float32) []byte {
+func (Encoder) AppendFloats32(dst []byte, vals []float32, precision int) []byte {
 	if len(vals) == 0 {
 		return append(dst, '[', ']')
 	}
 	dst = append(dst, '[')
-	dst = appendFloat(dst, float64(vals[0]), 32)
+	dst = appendFloat(dst, float64(vals[0]), 32, precision)
 	if len(vals) > 1 {
 		for _, val := range vals[1:] {
-			dst = appendFloat(append(dst, ','), float64(val), 32)
+			dst = appendFloat(append(dst, ','), float64(val), 32, precision)
 		}
 	}
 	dst = append(dst, ']')
@@ -357,21 +360,21 @@ func (Encoder) AppendFloats32(dst []byte, vals []float32) []byte {
 
 // AppendFloat64 converts the input float64 to a string and
 // appends the encoded string to the input byte slice.
-func (Encoder) AppendFloat64(dst []byte, val float64) []byte {
-	return appendFloat(dst, val, 64)
+func (Encoder) AppendFloat64(dst []byte, val float64, precision int) []byte {
+	return appendFloat(dst, val, 64, precision)
 }
 
 // AppendFloats64 encodes the input float64s to json and
 // appends the encoded string list to the input byte slice.
-func (Encoder) AppendFloats64(dst []byte, vals []float64) []byte {
+func (Encoder) AppendFloats64(dst []byte, vals []float64, precision int) []byte {
 	if len(vals) == 0 {
 		return append(dst, '[', ']')
 	}
 	dst = append(dst, '[')
-	dst = appendFloat(dst, vals[0], 64)
+	dst = appendFloat(dst, vals[0], 64, precision)
 	if len(vals) > 1 {
 		for _, val := range vals[1:] {
-			dst = appendFloat(append(dst, ','), val, 64)
+			dst = appendFloat(append(dst, ','), val, 64, precision)
 		}
 	}
 	dst = append(dst, ']')


Test patch:
diff --git a/internal/cbor/types_test.go b/internal/cbor/types_test.go
index 2fbb162..23093b1 100644
--- a/internal/cbor/types_test.go
+++ b/internal/cbor/types_test.go
@@ -169,7 +169,7 @@ var float32TestCases = []struct {
 
 func TestAppendFloat32(t *testing.T) {
 	for _, tc := range float32TestCases {
-		s := enc.AppendFloat32([]byte{}, tc.val)
+		s := enc.AppendFloat32([]byte{}, tc.val, -1)
 		got := string(s)
 		if got != tc.binary {
 			t.Errorf("AppendFloat32(%f)=0x%s, want: 0x%s",
@@ -306,9 +306,9 @@ func BenchmarkAppendFloat(b *testing.B) {
 			for i := 0; i < b.N; i++ {
 				switch str.sz {
 				case 4:
-					_ = enc.AppendFloat32(buf, float32(str.val))
+					_ = enc.AppendFloat32(buf, float32(str.val), -1)
 				case 8:
-					_ = enc.AppendFloat64(buf, str.val)
+					_ = enc.AppendFloat64(buf, str.val, -1)
 				}
 			}
 		})
diff --git a/internal/json/types_test.go b/internal/json/types_test.go
index 350affe..217cd3a 100644
--- a/internal/json/types_test.go
+++ b/internal/json/types_test.go
@@ -11,18 +11,20 @@ import (
 
 func TestAppendType(t *testing.T) {
 	w := map[string]func(interface{}) []byte{
-		"AppendInt":     func(v interface{}) []byte { return enc.AppendInt([]byte{}, v.(int)) },
-		"AppendInt8":    func(v interface{}) []byte { return enc.AppendInt8([]byte{}, v.(int8)) },
-		"AppendInt16":   func(v interface{}) []byte { return enc.AppendInt16([]byte{}, v.(int16)) },
-		"AppendInt32":   func(v interface{}) []byte { return enc.AppendInt32([]byte{}, v.(int32)) },
-		"AppendInt64":   func(v interface{}) []byte { return enc.AppendInt64([]byte{}, v.(int64)) },
-		"AppendUint":    func(v interface{}) []byte { return enc.AppendUint([]byte{}, v.(uint)) },
-		"AppendUint8":   func(v interface{}) []byte { return enc.AppendUint8([]byte{}, v.(uint8)) },
-		"AppendUint16":  func(v interface{}) []byte { return enc.AppendUint16([]byte{}, v.(uint16)) },
-		"AppendUint32":  func(v interface{}) []byte { return enc.AppendUint32([]byte{}, v.(uint32)) },
-		"AppendUint64":  func(v interface{}) []byte { return enc.AppendUint64([]byte{}, v.(uint64)) },
-		"AppendFloat32": func(v interface{}) []byte { return enc.AppendFloat32([]byte{}, v.(float32)) },
-		"AppendFloat64": func(v interface{}) []byte { return enc.AppendFloat64([]byte{}, v.(float64)) },
+		"AppendInt":                   func(v interface{}) []byte { return enc.AppendInt([]byte{}, v.(int)) },
+		"AppendInt8":                  func(v interface{}) []byte { return enc.AppendInt8([]byte{}, v.(int8)) },
+		"AppendInt16":                 func(v interface{}) []byte { return enc.AppendInt16([]byte{}, v.(int16)) },
+		"AppendInt32":                 func(v interface{}) []byte { return enc.AppendInt32([]byte{}, v.(int32)) },
+		"AppendInt64":                 func(v interface{}) []byte { return enc.AppendInt64([]byte{}, v.(int64)) },
+		"AppendUint":                  func(v interface{}) []byte { return enc.AppendUint([]byte{}, v.(uint)) },
+		"AppendUint8":                 func(v interface{}) []byte { return enc.AppendUint8([]byte{}, v.(uint8)) },
+		"AppendUint16":                func(v interface{}) []byte { return enc.AppendUint16([]byte{}, v.(uint16)) },
+		"AppendUint32":                func(v interface{}) []byte { return enc.AppendUint32([]byte{}, v.(uint32)) },
+		"AppendUint64":                func(v interface{}) []byte { return enc.AppendUint64([]byte{}, v.(uint64)) },
+		"AppendFloat32":               func(v interface{}) []byte { return enc.AppendFloat32([]byte{}, v.(float32), -1) },
+		"AppendFloat64":               func(v interface{}) []byte { return enc.AppendFloat64([]byte{}, v.(float64), -1) },
+		"AppendFloat32SmallPrecision": func(v interface{}) []byte { return enc.AppendFloat32([]byte{}, v.(float32), 1) },
+		"AppendFloat64SmallPrecision": func(v interface{}) []byte { return enc.AppendFloat64([]byte{}, v.(float64), 1) },
 	}
 	tests := []struct {
 		name  string
@@ -55,6 +57,9 @@ func TestAppendType(t *testing.T) {
 		{"AppendFloat64(-1.1)", "AppendFloat64", float64(-1.1), []byte(`-1.1`)},
 		{"AppendFloat64(1e20)", "AppendFloat64", float64(1e20), []byte(`100000000000000000000`)},
 		{"AppendFloat64(1e21)", "AppendFloat64", float64(1e21), []byte(`1e+21`)},
+
+		{"AppendFloat32SmallPrecision(-1.123)", "AppendFloat32SmallPrecision", float32(-1.123), []byte(`-1.1`)},
+		{"AppendFloat64SmallPrecision(-1.123)", "AppendFloat64SmallPrecision", float64(-1.123), []byte(`-1.1`)},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -301,7 +306,7 @@ func TestEncoder_AppendFloat64(t *testing.T) {
 	for _, tc := range float64Tests {
 		t.Run(tc.Name, func(t *testing.T) {
 			var b []byte
-			b = (Encoder{}).AppendFloat64(b, tc.Val)
+			b = (Encoder{}).AppendFloat64(b, tc.Val, -1)
 			if s := string(b); tc.Want != s {
 				t.Errorf("%q", s)
 			}
@@ -314,7 +319,7 @@ func FuzzEncoder_AppendFloat64(f *testing.F) {
 		f.Add(tc.Val)
 	}
 	f.Fuzz(func(t *testing.T, val float64) {
-		actual := (Encoder{}).AppendFloat64(nil, val)
+		actual := (Encoder{}).AppendFloat64(nil, val, -1)
 		if len(actual) == 0 {
 			t.Fatal("empty buffer")
 		}
@@ -447,7 +452,7 @@ func TestEncoder_AppendFloat32(t *testing.T) {
 	for _, tc := range float32Tests {
 		t.Run(tc.Name, func(t *testing.T) {
 			var b []byte
-			b = (Encoder{}).AppendFloat32(b, tc.Val)
+			b = (Encoder{}).AppendFloat32(b, tc.Val, -1)
 			if s := string(b); tc.Want != s {
 				t.Errorf("%q", s)
 			}
@@ -460,7 +465,7 @@ func FuzzEncoder_AppendFloat32(f *testing.F) {
 		f.Add(tc.Val)
 	}
 	f.Fuzz(func(t *testing.T, val float32) {
-		actual := (Encoder{}).AppendFloat32(nil, val)
+		actual := (Encoder{}).AppendFloat32(nil, val, -1)
 		if len(actual) == 0 {
 			t.Fatal("empty buffer")
 		}
@@ -528,7 +533,7 @@ func BenchmarkEncoder_AppendFloat32(b *testing.B) {
 
 	for i := 0; i < b.N; i++ {
 		for _, f := range floats {
-			dst = (Encoder{}).AppendFloat32(dst[:0], f)
+			dst = (Encoder{}).AppendFloat32(dst[:0], f, -1)
 		}
 	}
 }
@@ -542,7 +547,7 @@ func BenchmarkEncoder_AppendFloat64(b *testing.B) {
 
 	for i := 0; i < b.N; i++ {
 		for _, f := range floats {
-			dst = (Encoder{}).AppendFloat64(dst[:0], f)
+			dst = (Encoder{}).AppendFloat64(dst[:0], f, -1)
 		}
 	}
 }