Skip to content

Commit

Permalink
feat: rework float handling
Browse files Browse the repository at this point in the history
* Don't handle NaN or Inf
* Use stdlib float64 formatting
  • Loading branch information
ernado committed Oct 31, 2021
1 parent 9ec31cc commit 1073601
Show file tree
Hide file tree
Showing 21 changed files with 186 additions and 171 deletions.
21 changes: 6 additions & 15 deletions any.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ func (d *Decoder) Any() (Any, error) {
}

// Any writes Any value.
func (e *Encoder) Any(a Any) error {
return a.Write(e)
func (e *Encoder) Any(a Any) {
a.Write(e)
}

func (v *Any) Read(d *Decoder) error {
Expand Down Expand Up @@ -136,17 +136,15 @@ func (v *Any) Read(d *Decoder) error {
}

// Write json representation of Any to Encoder.
func (v Any) Write(w *Encoder) error {
func (v Any) Write(w *Encoder) {
if v.KeyValid {
w.ObjField(v.Key)
}
switch v.Type {
case AnyStr:
w.Str(v.Str)
case AnyFloat:
if err := w.Float64(v.Float); err != nil {
return err
}
w.Float64(v.Float)
case AnyInt:
w.Int64(v.Int)
case AnyBool:
Expand All @@ -159,9 +157,7 @@ func (v Any) Write(w *Encoder) error {
if i != 0 {
w.More()
}
if err := c.Write(w); err != nil {
return err
}
c.Write(w)
}
w.ArrEnd()
case AnyObj:
Expand All @@ -170,15 +166,10 @@ func (v Any) Write(w *Encoder) error {
if i != 0 {
w.More()
}
if err := c.Write(w); err != nil {
return err
}
c.Write(w)
}
w.ObjEnd()
default:
return xerrors.Errorf("unexpected type %d", v.Type)
}
return nil
}

func (v Any) String() string {
Expand Down
12 changes: 6 additions & 6 deletions any_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,25 +40,25 @@ func TestAny_Read(t *testing.T) {
assert.NoError(t, v.Read(r))
assert.Equal(t, "{foo: {bar: 1, baz: [1, 2, f3.14], 200: null, f: 's', t: true, <blank>: ''}}", v.String())

e := NewEncoder()
require.NoError(t, e.Any(v))
e := GetEncoder()
e.Any(v)
require.Equal(t, input, e.String(), "encoded value should equal to input")
})
t.Run("Inputs", func(t *testing.T) {
for _, tt := range []struct {
Input string
}{
{Input: "1"},
{Input: "0.0"},
{Input: "0"},
} {
t.Run(tt.Input, func(t *testing.T) {
input := []byte(tt.Input)
r := DecodeBytes(input)
v, err := r.Any()
require.NoError(t, err)

e := NewEncoder()
require.NoError(t, v.Write(e))
e := GetEncoder()
v.Write(e)
require.Equal(t, tt.Input, e.String(), "encoded value should equal to input")

var otherValue Any
Expand All @@ -76,7 +76,7 @@ func TestAny_Read(t *testing.T) {

func BenchmarkAny(b *testing.B) {
data := []byte(`[true, null, false, 100, "false"]`)
r := NewDecoder()
r := GetDecoder()

b.ReportAllocs()
b.SetBytes(int64(len(data)))
Expand Down
2 changes: 1 addition & 1 deletion bool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func Test_false(t *testing.T) {

func Test_write_true_false(t *testing.T) {
should := require.New(t)
w := NewEncoder()
w := GetEncoder()
w.True()
w.False()
w.Bool(false)
Expand Down
10 changes: 2 additions & 8 deletions dec.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,6 @@ type Decoder struct {
depth int
}

// NewDecoder creates an empty Decoder.
//
// Use Decoder.Reset or Decoder.ResetBytes.
func NewDecoder() *Decoder {
return &Decoder{}
}

// Decode creates a Decoder that reads json from io.Reader.
func Decode(reader io.Reader, bufSize int) *Decoder {
return &Decoder{
Expand Down Expand Up @@ -141,7 +134,8 @@ func (d *Decoder) Reset(reader io.Reader) *Decoder {
// Reads from reader need buffer.
if d.buf == nil || cap(d.buf) == 0 {
// Allocate new buffer if none.
d.buf = make([]byte, 1024)
const defaultBuf = 512
d.buf = make([]byte, defaultBuf)
}
if len(d.buf) == 0 {
// Set buffer to full capacity if needed.
Expand Down
2 changes: 2 additions & 0 deletions dec_float.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"golang.org/x/xerrors"
)

var pow10 = []uint64{1, 10, 100, 1000, 10000, 100000, 1000000}

var floatDigits []int8

const invalidCharForNumber = int8(-1)
Expand Down
12 changes: 12 additions & 0 deletions dec_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package jx

import (
"bytes"
"testing"

"github.com/stretchr/testify/require"
)

func TestType_String(t *testing.T) {
Expand All @@ -20,3 +23,12 @@ func TestType_String(t *testing.T) {
t.Error("unexpected met types")
}
}

func TestDecoder_Reset(t *testing.T) {
var d Decoder
d.ResetBytes([]byte{})
d.Reset(bytes.NewBufferString(`true`))
v, err := d.Bool()
require.NoError(t, err)
require.True(t, v)
}
57 changes: 26 additions & 31 deletions enc.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import (
)

// Encoder encodes json to underlying buffer.
//
// Zero value is valid.
type Encoder struct {
buf []byte
ident int
curIdent int
buf []byte // underlying buffer
ident int // indentation step
spaces int // count of spaces
}

// Write implements io.Writer.
Expand All @@ -33,15 +35,6 @@ func (e *Encoder) String() string {
return string(e.Bytes())
}

// NewEncoder creates new encoder.
func NewEncoder() *Encoder {
const defaultBuf = 256

return &Encoder{
buf: make([]byte, 0, defaultBuf),
}
}

// Reset resets underlying buffer.
func (e *Encoder) Reset() {
e.buf = e.buf[:0]
Expand Down Expand Up @@ -84,17 +77,17 @@ func (e *Encoder) RawBytes(b []byte) {
e.buf = append(e.buf, b...)
}

// Null write null to stream.
// Null writes null to stream.
func (e *Encoder) Null() {
e.fourBytes('n', 'u', 'l', 'l')
}

// True write true to stream.
// True writes true.
func (e *Encoder) True() {
e.fourBytes('t', 'r', 'u', 'e')
}

// False writes false to stream.
// False writes false.
func (e *Encoder) False() {
e.fiveBytes('f', 'a', 'l', 's', 'e')
}
Expand All @@ -108,68 +101,70 @@ func (e *Encoder) Bool(val bool) {
}
}

// ObjStart writes { with possible indention.
// ObjStart writes object start, performing indentation if needed
func (e *Encoder) ObjStart() {
e.curIdent += e.ident
e.spaces += e.ident
e.byte('{')
e.writeIdent(0)
}

// ObjField write "field": with possible indention.
// ObjField writes field name and colon.
//
// For non-zero indentation also writes single space after colon.
func (e *Encoder) ObjField(field string) {
e.Str(field)
if e.curIdent > 0 {
if e.spaces > 0 {
e.twoBytes(':', ' ')
} else {
e.byte(':')
}
}

// ObjEnd write } with possible indention
// ObjEnd writes end of object token, performing indentation if needed.
func (e *Encoder) ObjEnd() {
e.writeIdent(e.ident)
e.curIdent -= e.ident
e.spaces -= e.ident
e.byte('}')
}

// ObjEmpty write {}
// ObjEmpty writes empty object.
func (e *Encoder) ObjEmpty() {
e.byte('{')
e.byte('}')
}

// More write , with possible indention
// More writes comma, performing indentation if needed.
func (e *Encoder) More() {
e.byte(',')
e.writeIdent(0)
}

// ArrStart writes [ with possible indention.
// ArrStart writes start of array, performing indentation if needed.
func (e *Encoder) ArrStart() {
e.curIdent += e.ident
e.spaces += e.ident
e.byte('[')
e.writeIdent(0)
}

// ArrEmpty writes [].
// ArrEmpty writes empty array.
func (e *Encoder) ArrEmpty() {
e.twoBytes('[', ']')
}

// ArrEnd writes ] with possible indention.
// ArrEnd writes end of array, performing indentation if needed.
func (e *Encoder) ArrEnd() {
e.writeIdent(e.ident)
e.curIdent -= e.ident
e.spaces -= e.ident
e.byte(']')
}

func (e *Encoder) writeIdent(delta int) {
if e.curIdent == 0 {
if e.spaces == 0 {
return
}
e.byte('\n')
toWrite := e.curIdent - delta
for i := 0; i < toWrite; i++ {
spaces := e.spaces - delta
for i := 0; i < spaces; i++ {
e.buf = append(e.buf, ' ')
}
}
21 changes: 9 additions & 12 deletions enc_bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,21 @@ import (
"testing"
)

func Benchmark_stream_encode_big_object(b *testing.B) {
func BenchmarkEncoderBigObject(b *testing.B) {
b.ReportAllocs()

e := GetEncoder()
encodeObject(e)
b.SetBytes(int64(len(e.Bytes())))

b.ResetTimer()
for i := 0; i < b.N; i++ {
e.Reset()
if err := encodeObject(e); err != nil {
b.Fatal(err)
}
encodeObject(e)
}
}

func encodeObject(w *Encoder) error {
func encodeObject(w *Encoder) {
w.ObjStart()

w.ObjField("objectId")
Expand All @@ -43,14 +45,10 @@ func encodeObject(w *Encoder) error {
{
w.ObjStart()
w.ObjField("latitude")
if err := w.Float64(-154.550817); err != nil {
return err
}
w.Float64(-154.550817)
w.More()
w.ObjField("longitude")
if err := w.Float64(-84.176159); err != nil {
return err
}
w.Float64(-84.176159)
w.ObjEnd()
}
w.ObjEnd()
Expand Down Expand Up @@ -83,7 +81,6 @@ func encodeObject(w *Encoder) error {
}

w.ObjEnd()
return nil
}

type field struct{ key, val string }
Expand Down
Loading

0 comments on commit 1073601

Please sign in to comment.