Skip to content

Commit

Permalink
Use json for writing floats as strings (open-telemetry#4934)
Browse files Browse the repository at this point in the history
* use json for writing floats as strings

* avoid using reflection for floats

* extra unit test

* fix rebase

Co-authored-by: Bogdan Drutu <bogdandrutu@gmail.com>
  • Loading branch information
2 people authored and Nicholaswang committed Jun 7, 2022
1 parent 7efc520 commit 8b1a752
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

- Remove `Type` funcs in pdata (#4933)
- Remove all deprecated funcs/structs from v0.46.0 (#4995)
- AsString for pdata.AttributeValue now returns the JSON-encoded string of floats. (#4934)

### 🚩 Deprecations 🚩

Expand Down
34 changes: 33 additions & 1 deletion model/internal/pdata/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"math"
"sort"
"strconv"

Expand Down Expand Up @@ -379,7 +380,7 @@ func (v Value) AsString() string {
return strconv.FormatBool(v.BoolVal())

case ValueTypeDouble:
return strconv.FormatFloat(v.DoubleVal(), 'f', -1, 64)
return float64AsString(v.DoubleVal())

case ValueTypeInt:
return strconv.FormatInt(v.IntVal(), 10)
Expand All @@ -400,6 +401,37 @@ func (v Value) AsString() string {
}
}

// See https://cs.opensource.google/go/go/+/refs/tags/go1.17.7:src/encoding/json/encode.go;l=585.
// This allows us to avoid using reflection.
func float64AsString(f float64) string {
if math.IsInf(f, 0) || math.IsNaN(f) {
return fmt.Sprintf("json: unsupported value: %s", strconv.FormatFloat(f, 'g', -1, int(64)))
}

// Convert as if by ES6 number to string conversion.
// This matches most other JSON generators.
// See golang.org/issue/6384 and golang.org/issue/14135.
// Like fmt %g, but the exponent cutoffs are different
// and exponents themselves are not padded to two digits.
scratch := [64]byte{}
b := scratch[:0]
abs := math.Abs(f)
fmt := byte('f')
if abs != 0 && (abs < 1e-6 || abs >= 1e21) {
fmt = 'e'
}
b = strconv.AppendFloat(b, f, fmt, -1, int(64))
if fmt == 'e' {
// clean up e-09 to e-9
n := len(b)
if n >= 4 && b[n-4] == 'e' && b[n-3] == '-' && b[n-2] == '0' {
b[n-2] = b[n-1]
b = b[:n-1]
}
}
return string(b)
}

func newAttributeKeyValueString(k string, v string) otlpcommon.KeyValue {
orig := otlpcommon.KeyValue{Key: k}
akv := Value{&orig.Value}
Expand Down
20 changes: 20 additions & 0 deletions model/internal/pdata/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package pdata
import (
"encoding/base64"
"fmt"
"math"
"strconv"
"testing"

Expand Down Expand Up @@ -780,6 +781,15 @@ func BenchmarkAttributeValue_SetIntVal(b *testing.B) {
}
}

func BenchmarkAttributeValueFloat_AsString(b *testing.B) {
av := NewValueDouble(2359871345.583429543)

b.ResetTimer()
for n := 0; n < b.N; n++ {
av.AsString()
}
}

func BenchmarkAttributeMap_Range(b *testing.B) {
const numElements = 20
rawOrig := make([]otlpcommon.KeyValue, numElements)
Expand Down Expand Up @@ -1023,6 +1033,16 @@ func TestAsString(t *testing.T) {
input: NewValueDouble(1.61803399),
expected: "1.61803399",
},
{
name: "small float64",
input: NewValueDouble(.000000009),
expected: "9e-9",
},
{
name: "bad float64",
input: NewValueDouble(math.Inf(1)),
expected: "json: unsupported value: +Inf",
},
{
name: "boolean",
input: NewValueBool(true),
Expand Down

0 comments on commit 8b1a752

Please sign in to comment.