From 941fb4951ccf0f4cd3470c33789e40ee561cf129 Mon Sep 17 00:00:00 2001 From: Aleksandr Razumov Date: Fri, 5 Nov 2021 18:27:55 +0300 Subject: [PATCH] feat(num): rework --- dec_num.go | 22 ++-------- enc_num.go | 10 +---- num.go | 113 ++++++++++++---------------------------------------- num_test.go | 72 ++++++++++----------------------- 4 files changed, 53 insertions(+), 164 deletions(-) diff --git a/dec_num.go b/dec_num.go index 2ed1421..d014786 100644 --- a/dec_num.go +++ b/dec_num.go @@ -11,29 +11,27 @@ func (d *Decoder) Num() (Num, error) { // NumTo decodes number into Num. func (d *Decoder) NumTo(v Num) (Num, error) { - var str bool switch d.Next() { case String: // Consume start of the string. d.head++ - str = true case Number: // float or integer default: return v, errors.Errorf("unexpected %s", d.Next()) } if d.reader == nil { // Can use underlying buffer directly. - v.Value = d.number() + v = d.number() } else { - buf, err := d.numberAppend(v.Value[:0]) + buf, err := d.numberAppend(v[:0]) if err != nil { return v, errors.Wrap(err, "decode") } - v.Value = buf + v = buf } var dot bool - for _, c := range v.Value { + for _, c := range v { if c != '.' { continue } @@ -41,18 +39,6 @@ func (d *Decoder) NumTo(v Num) (Num, error) { return v, errors.New("multiple dots in number") } dot = true - break - } - if dot { - v.Format = NumFormatFloat - if str { - v.Format = NumFormatFloatStr - } - } else { - v.Format = NumFormatInt - if str { - v.Format = NumFormatIntStr - } } // TODO(ernado): Additional validity checks diff --git a/enc_num.go b/enc_num.go index a52bf07..fe32d96 100644 --- a/enc_num.go +++ b/enc_num.go @@ -2,15 +2,9 @@ package jx // Num encodes number. func (e *Encoder) Num(v Num) { - if v.Format.Invalid() { + if len(v) == 0 { e.Null() return } - if v.Format.Str() { - e.byte('"') - } - e.RawBytes(v.Value) - if v.Format.Str() { - e.byte('"') - } + e.RawBytes(v) } diff --git a/num.go b/num.go index 181f603..574ebc6 100644 --- a/num.go +++ b/num.go @@ -2,85 +2,31 @@ package jx import ( "bytes" - "strings" "github.com/ogen-go/errors" ) -// NumFormat is format of Num.Value. -type NumFormat uint8 - -// Possible formats of Num. -const ( - NumFormatInvalid NumFormat = iota // invalid or blank - NumFormatInt // 1234 - NumFormatFloat // 1.234 - NumFormatIntStr // "1234" - NumFormatFloatStr // "1.234" -) - -// Float reports whether format is float. -func (f NumFormat) Float() bool { - return f == NumFormatFloat || f == NumFormatFloatStr -} - -// Invalid reports whether format is invalid. -func (f NumFormat) Invalid() bool { - return f == NumFormatInvalid || f > NumFormatFloatStr -} - -// Int reports whether format is integer. -func (f NumFormat) Int() bool { - return f == NumFormatInt || f == NumFormatIntStr -} - -func (f NumFormat) String() string { - switch f { - case NumFormatInt: - return "integer" - case NumFormatFloat: - return "float" - case NumFormatIntStr: - return "integer string" - case NumFormatFloatStr: - return "float string" - default: - return "invalid" - } -} - -// Str reports whether format is string integer or float. -func (f NumFormat) Str() bool { - return f == NumFormatIntStr || f == NumFormatFloatStr -} - // Num represents number, which can be raw json number or string of number. // -// Zero value is invalid. -type Num struct { - // Format is number format for Value. - Format NumFormat - // Value is raw json of number, only digits or float characters. - // - // If Num is string number, Value does not contain quotes. - Value []byte -} +// Same as Raw, but with number invariants. +type Num []byte func (n Num) dec() Decoder { return Decoder{ - buf: n.Value, - tail: len(n.Value), + buf: n, + tail: len(n), } } -func (n Num) floatAsInt() error { - if n.Format.Int() { - return nil - } +// Str reports whether Num is string number. +func (n Num) Str() bool { + return len(n) > 0 && n[0] == '"' +} +func (n Num) floatAsInt() error { // Allow decoding floats with zero fractional, like 1.0 as 1. var dot bool - for _, c := range n.Value { + for _, c := range n { if c == '.' { dot = true continue @@ -123,35 +69,31 @@ func (n Num) Float64() (float64, error) { // Equal reports whether numbers are strictly equal, including their formats. func (n Num) Equal(v Num) bool { - if n.Format != v.Format { - return false - } - return bytes.Equal(n.Value, v.Value) + return bytes.Equal(n, v) } func (n Num) String() string { - if n.Format.Invalid() { + if len(n) == 0 { return "" } - var b strings.Builder - if n.Format.Str() { - b.WriteByte('"') - } - _, _ = b.Write(n.Value) - if n.Format.Str() { - b.WriteByte('"') - } - return b.String() + return string(n) } // Sign reports sign of number. // // 0 is zero, 1 is positive, -1 is negative. func (n Num) Sign() int { - if n.Format.Invalid() || len(n.Value) == 0 { + if len(n) == 0 { return 0 } - switch n.Value[0] { + c := n[0] + if c == '"' { + if len(n) < 2 { + return 0 + } + c = n[1] + } + switch c { case '-': return -1 case '0': @@ -169,16 +111,13 @@ func (n Num) Negative() bool { return n.Sign() < 0 } // Zero reports whether number is zero. func (n Num) Zero() bool { - if n.Format.Invalid() || len(n.Value) == 0 { + if len(n) == 0 { return false } - if len(n.Value) == 1 { - return n.Value[0] == '0' - } - if n.Format.Int() { - return false + if len(n) == 1 { + return n[0] == '0' } - for _, c := range n.Value { + for _, c := range n { switch c { case '.', '0': continue diff --git a/num_test.go b/num_test.go index afa2475..463ad2f 100644 --- a/num_test.go +++ b/num_test.go @@ -9,10 +9,7 @@ import ( func TestEncoder_Num(t *testing.T) { var e Encoder - e.Num(Num{ - Format: NumFormatInt, - Value: []byte{'1', '2', '3'}, - }) + e.Num(Num{'1', '2', '3'}) require.Equal(t, e.String(), "123") } @@ -26,18 +23,12 @@ func TestNum(t *testing.T) { { Name: "Int", String: "-12", - Value: Num{ - Format: NumFormatInt, - Value: []byte("-12"), - }, + Value: Num("-12"), }, { Name: "IntStr", String: `"-12"`, - Value: Num{ - Format: NumFormatIntStr, - Value: []byte("-12"), - }, + Value: Num(`"-12"`), }, } { t.Run(cc.Name, func(t *testing.T) { @@ -48,8 +39,6 @@ func TestNum(t *testing.T) { t.Run("ZeroValue", func(t *testing.T) { // Zero value is invalid because there is no Num.Value. var v Num - require.Equal(t, NumFormatInvalid, v.Format) - require.True(t, v.Format.Invalid()) require.False(t, v.Zero()) require.False(t, v.Positive()) require.False(t, v.Negative()) @@ -57,14 +46,9 @@ func TestNum(t *testing.T) { }) t.Run("Integer", func(t *testing.T) { t.Run("Int", func(t *testing.T) { - v := Num{ - Format: NumFormatInt, - Value: []byte{'1', '2', '3'}, - } + v := Num{'1', '2', '3'} t.Run("Methods", func(t *testing.T) { assert.True(t, v.Positive()) - assert.True(t, v.Format.Int()) - assert.False(t, v.Format.Invalid()) assert.False(t, v.Negative()) assert.False(t, v.Zero()) assert.Equal(t, 1, v.Sign()) @@ -84,10 +68,7 @@ func TestNum(t *testing.T) { }) t.Run("FloatAsInt", func(t *testing.T) { t.Run("Positive", func(t *testing.T) { - v := Num{ - Format: NumFormatFloat, - Value: []byte{'1', '2', '3', '.', '0'}, - } + v := Num{'1', '2', '3', '.', '0'} n, err := v.Int64() require.NoError(t, err) require.Equal(t, int64(123), n) @@ -101,10 +82,7 @@ func TestNum(t *testing.T) { require.InEpsilon(t, 123, f, epsilon) }) t.Run("Negative", func(t *testing.T) { - v := Num{ - Format: NumFormatFloat, - Value: []byte{'1', '2', '3', '.', '0', '0', '1'}, - } + v := Num{'1', '2', '3', '.', '0', '0', '1'} _, err := v.Int64() require.Error(t, err) }) @@ -112,7 +90,6 @@ func TestNum(t *testing.T) { t.Run("Decode", func(t *testing.T) { n, err := DecodeStr("12345").NumTo(Num{}) require.NoError(t, err) - require.Equal(t, NumFormatInt, n.Format) require.Equal(t, "12345", n.String()) }) }) @@ -121,10 +98,7 @@ func TestNum(t *testing.T) { s = `1.23` f = 1.23 ) - v := Num{ - Format: NumFormatFloat, - Value: []byte(s), - } + v := Num(s) t.Run("Encode", func(t *testing.T) { var e Encoder e.Num(v) @@ -137,13 +111,10 @@ func TestNum(t *testing.T) { t.Run("Decode", func(t *testing.T) { n, err := DecodeStr(s).NumTo(Num{}) require.NoError(t, err) - require.Equal(t, NumFormatFloat, n.Format) require.Equal(t, s, n.String()) }) t.Run("Methods", func(t *testing.T) { assert.True(t, v.Positive()) - assert.True(t, v.Format.Float()) - assert.False(t, v.Format.Invalid()) assert.False(t, v.Negative()) assert.False(t, v.Zero()) assert.Equal(t, 1, v.Sign()) @@ -155,10 +126,7 @@ func TestNum(t *testing.T) { func BenchmarkNum(b *testing.B) { b.Run("FloatAsInt", func(b *testing.B) { b.Run("Integer", func(b *testing.B) { - v := Num{ - Format: NumFormatFloat, - Value: []byte{'1', '2', '3', '5', '7', '.', '0'}, - } + v := Num{'1', '2', '3', '5', '7', '.', '0'} b.ReportAllocs() for i := 0; i < b.N; i++ { if _, err := v.Int64(); err != nil { @@ -166,12 +134,21 @@ func BenchmarkNum(b *testing.B) { } } }) + b.Run("Float30Chars", func(b *testing.B) { + var v Num + for i := 0; i < 30; i++ { + v = append(v, '1') + } + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if err := v.floatAsInt(); err != nil { + b.Fatal(err) + } + } + }) }) b.Run("Integer", func(b *testing.B) { - v := Num{ - Format: NumFormatInt, - Value: []byte{'1', '2', '3', '5', '7'}, - } + v := Num{'1', '2', '3', '5', '7'} b.Run("Positive", func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { @@ -196,12 +173,5 @@ func BenchmarkNum(b *testing.B) { e.Reset() } }) - b.Run("Format.Invalid", func(b *testing.B) { - for i := 0; i < b.N; i++ { - if v.Format.Invalid() { - b.Fatal("invalid") - } - } - }) }) }