Encode all numbers as float64
All numbers in encoded keys are now encoded into float64, ensuring that decimal numbers are sorted alongside integers. The maximum number able to be stored as a float64, without losing precision, is now (1<<53 - 1) - the same maximum number possible in javascript.
This commit is contained in:
parent
0f420d2cae
commit
529ddbbec2
6 changed files with 194 additions and 98 deletions
|
@ -99,12 +99,26 @@ func (e *encoder) Encode(items ...interface{}) {
|
|||
e.w.Write(value)
|
||||
e.w.Write(bTERM)
|
||||
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
||||
case int, int8, int16, int32, int64:
|
||||
|
||||
e.w.Write(bNUMBER)
|
||||
e.w.Write(value)
|
||||
e.w.Write(bTERM)
|
||||
|
||||
case uint, uint8, uint16, uint32, uint64:
|
||||
|
||||
e.w.Write(bNUMBER)
|
||||
e.w.Write(value)
|
||||
e.w.Write(bTERM)
|
||||
|
||||
case []time.Time:
|
||||
|
||||
e.w.Write(bARRAY)
|
||||
for _, val := range value {
|
||||
e.Encode(val)
|
||||
}
|
||||
e.w.Write(bTERM)
|
||||
|
||||
case []bool:
|
||||
|
||||
e.w.Write(bARRAY)
|
||||
|
|
|
@ -22,10 +22,11 @@ type Full struct {
|
|||
F bool // false
|
||||
S string // string
|
||||
T time.Time // time.Time
|
||||
N64 int64 // negative int64
|
||||
N32 int32 // negative int32
|
||||
N16 int16 // negative int16
|
||||
N8 int8 // negative int8
|
||||
NI64 int64 // negative int64
|
||||
NI32 int32 // negative int32
|
||||
NI16 int16 // negative int16
|
||||
NI8 int8 // negative int8
|
||||
NI int // negative int
|
||||
I int // positive int
|
||||
I8 int8 // positive int8
|
||||
I16 int16 // positive int16
|
||||
|
@ -36,16 +37,19 @@ type Full struct {
|
|||
UI16 uint16 // positive uint16
|
||||
UI32 uint32 // positive uint32
|
||||
UI64 uint64 // positive uint64
|
||||
NF32 float32 // negative float32
|
||||
NF64 float64 // negative float64
|
||||
NF32 float32 // negative float32
|
||||
F32 float32 // positive float32
|
||||
F64 float64 // positive float64
|
||||
AB []bool // bool array
|
||||
AS []string // string array
|
||||
AT []time.Time // time array
|
||||
AI []int // int array
|
||||
AI8 []int8 // int8 array
|
||||
AI16 []int16 // int16 array
|
||||
AI32 []int32 // int32 array
|
||||
AI64 []int64 // int64 array
|
||||
AUI []uint // uint array
|
||||
AUI8 []uint8 // uint8 array
|
||||
AUI16 []uint16 // uint16 array
|
||||
AUI32 []uint32 // uint32 array
|
||||
|
@ -54,7 +58,7 @@ type Full struct {
|
|||
AF64 []float64 // float64 array
|
||||
IN interface{} // interface{}
|
||||
IB interface{} // interface{} true
|
||||
IF interface{} // interface{} talse
|
||||
IF interface{} // interface{} false
|
||||
IT interface{} // interface{} time.Time
|
||||
II interface{} // interface{} number
|
||||
ID interface{} // interface{} double
|
||||
|
@ -70,13 +74,13 @@ func (f *Full) Encode() []byte {
|
|||
|
||||
return encode(
|
||||
f.N, f.B, f.F, f.S, f.T,
|
||||
f.N64, f.N32, f.N16, f.N8,
|
||||
f.NI64, f.NI32, f.NI16, f.NI8, f.NI,
|
||||
f.I, f.I8, f.I16, f.I32, f.I64,
|
||||
f.UI, f.UI8, f.UI16, f.UI32, f.UI64,
|
||||
f.NF32, f.NF64, f.F32, f.F64,
|
||||
f.AB, f.AS,
|
||||
f.AI8, f.AI16, f.AI32, f.AI64,
|
||||
f.AUI8, f.AUI16, f.AUI32, f.AUI64,
|
||||
f.NF64, f.NF32, f.F32, f.F64,
|
||||
f.AB, f.AS, f.AT,
|
||||
f.AI, f.AI8, f.AI16, f.AI32, f.AI64,
|
||||
f.AUI, f.AUI8, f.AUI16, f.AUI32, f.AUI64,
|
||||
f.AF32, f.AF64,
|
||||
f.IN, f.IB, f.IF, f.IT, f.II, f.ID, f.INA, f.AIN,
|
||||
)
|
||||
|
@ -88,13 +92,13 @@ func (f *Full) Decode(data []byte) {
|
|||
decode(
|
||||
data,
|
||||
&f.N, &f.B, &f.F, &f.S, &f.T,
|
||||
&f.N64, &f.N32, &f.N16, &f.N8,
|
||||
&f.NI64, &f.NI32, &f.NI16, &f.NI8, &f.NI,
|
||||
&f.I, &f.I8, &f.I16, &f.I32, &f.I64,
|
||||
&f.UI, &f.UI8, &f.UI16, &f.UI32, &f.UI64,
|
||||
&f.NF32, &f.NF64, &f.F32, &f.F64,
|
||||
&f.AB, &f.AS,
|
||||
&f.AI8, &f.AI16, &f.AI32, &f.AI64,
|
||||
&f.AUI8, &f.AUI16, &f.AUI32, &f.AUI64,
|
||||
&f.NF64, &f.NF32, &f.F32, &f.F64,
|
||||
&f.AB, &f.AS, &f.AT,
|
||||
&f.AI, &f.AI8, &f.AI16, &f.AI32, &f.AI64,
|
||||
&f.AUI, &f.AUI8, &f.AUI16, &f.AUI32, &f.AUI64,
|
||||
&f.AF32, &f.AF64,
|
||||
&f.IN, &f.IB, &f.IF, &f.IT, &f.II, &f.ID, &f.INA, &f.AIN,
|
||||
)
|
||||
|
|
|
@ -62,6 +62,13 @@ var (
|
|||
bSUFFIX = []byte("\x08")
|
||||
)
|
||||
|
||||
const (
|
||||
// MinNumber is the minimum number to be encoded and decoded
|
||||
MinNumber = -1 << 53
|
||||
// MaxNumber is the maximum number to be encoded and decoded
|
||||
MaxNumber = 1<<53 - 1
|
||||
)
|
||||
|
||||
// Key ...
|
||||
type Key interface {
|
||||
String() string
|
||||
|
|
|
@ -17,6 +17,7 @@ package keys
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -171,49 +172,53 @@ func TestMain(t *testing.T) {
|
|||
str: "Test key",
|
||||
new: &Full{},
|
||||
obj: &Full{
|
||||
N: nil,
|
||||
B: true,
|
||||
F: false,
|
||||
S: "Test",
|
||||
T: clock,
|
||||
N64: -9223372036854775807,
|
||||
N32: -2147483647,
|
||||
N16: -32767,
|
||||
N8: -127,
|
||||
I: 1,
|
||||
I8: 127,
|
||||
I16: 32767,
|
||||
I32: 2147483647,
|
||||
I64: 9223372036854775807,
|
||||
UI: 1,
|
||||
UI8: 255,
|
||||
UI16: 65535,
|
||||
UI32: 4294967295,
|
||||
UI64: 18446744073709551615,
|
||||
// NF32: -0.00001,
|
||||
// NF64: -0.00002,
|
||||
// F32: 0.00001,
|
||||
// F64: 0.00002,
|
||||
N: nil,
|
||||
B: true,
|
||||
F: false,
|
||||
S: "Test",
|
||||
T: clock,
|
||||
NI64: int64(MinNumber),
|
||||
NI32: math.MinInt32,
|
||||
NI16: math.MinInt16,
|
||||
NI8: math.MinInt8,
|
||||
NI: -1,
|
||||
I: 1,
|
||||
I8: math.MaxInt8,
|
||||
I16: math.MaxInt16,
|
||||
I32: math.MaxInt32,
|
||||
I64: int64(MaxNumber),
|
||||
UI: 1,
|
||||
UI8: math.MaxUint8,
|
||||
UI16: math.MaxUint16,
|
||||
UI32: math.MaxUint32,
|
||||
UI64: uint64(MaxNumber),
|
||||
NF64: -math.MaxFloat64,
|
||||
NF32: -math.MaxFloat32,
|
||||
F32: math.MaxFloat32,
|
||||
F64: math.MaxFloat64,
|
||||
AB: []bool{true, false},
|
||||
AS: []string{"A", "B", "C"},
|
||||
AI8: []int8{127},
|
||||
AI16: []int16{32767},
|
||||
AI32: []int32{2147483647},
|
||||
AI64: []int64{9223372036854775807},
|
||||
AUI8: []uint8{127},
|
||||
AUI16: []uint16{32767},
|
||||
AUI32: []uint32{2147483647},
|
||||
AUI64: []uint64{9223372036854775807},
|
||||
// AF32: []float32{0.1, 0.2, 0.3},
|
||||
// AF64: []float64{0.1, 0.2, 0.3},
|
||||
IN: "Test",
|
||||
IB: true,
|
||||
IF: false,
|
||||
IT: clock,
|
||||
II: int64(19387),
|
||||
// ID: float64(183784.13413),
|
||||
// INA: []interface{}{true, false, nil, "Test", clock, int64(192), 0.1, 0.2, 0.3},
|
||||
// AIN: []interface{}{true, false, nil, "Test", clock, int64(192), int64(9223372036854775807), 0.1, 0.2, 0.3},
|
||||
AT: []time.Time{clock, clock, clock},
|
||||
AI: []int{1},
|
||||
AI8: []int8{math.MaxInt8},
|
||||
AI16: []int16{math.MaxInt16},
|
||||
AI32: []int32{math.MaxInt32},
|
||||
AI64: []int64{int64(MaxNumber)},
|
||||
AUI: []uint{1},
|
||||
AUI8: []uint8{math.MaxUint8},
|
||||
AUI16: []uint16{math.MaxUint16},
|
||||
AUI32: []uint32{math.MaxUint32},
|
||||
AUI64: []uint64{uint64(MaxNumber)},
|
||||
AF32: []float32{1.1, 1.2, 1.3},
|
||||
AF64: []float64{1.1, 1.2, 1.3},
|
||||
IN: "Test",
|
||||
IB: true,
|
||||
IF: false,
|
||||
IT: clock,
|
||||
II: float64(19387),
|
||||
ID: float64(183784.13413),
|
||||
INA: []interface{}{true, false, nil, "Test", clock, float64(192), 1.1, 1.2, 1.3},
|
||||
AIN: []interface{}{true, false, nil, "Test", clock, float64(192), float64(MaxNumber), 1.1, 1.2, 1.3, []interface{}{"Test"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -235,13 +240,13 @@ func TestMain(t *testing.T) {
|
|||
&Thing{KV: "kv", NS: "ns", DB: "db", TB: "person", ID: 2},
|
||||
&Thing{KV: "kv", NS: "ns", DB: "db", TB: "person", ID: 12},
|
||||
&Thing{KV: "kv", NS: "ns", DB: "db", TB: "person", ID: 127},
|
||||
&Thing{KV: "kv", NS: "ns", DB: "db", TB: "person", ID: int8(127)},
|
||||
&Thing{KV: "kv", NS: "ns", DB: "db", TB: "person", ID: int8(math.MaxInt8)},
|
||||
&Thing{KV: "kv", NS: "ns", DB: "db", TB: "person", ID: 32767},
|
||||
&Thing{KV: "kv", NS: "ns", DB: "db", TB: "person", ID: int16(32767)},
|
||||
&Thing{KV: "kv", NS: "ns", DB: "db", TB: "person", ID: int16(math.MaxInt16)},
|
||||
&Thing{KV: "kv", NS: "ns", DB: "db", TB: "person", ID: 2147483647},
|
||||
&Thing{KV: "kv", NS: "ns", DB: "db", TB: "person", ID: int32(2147483647)},
|
||||
&Thing{KV: "kv", NS: "ns", DB: "db", TB: "person", ID: int32(math.MaxInt32)},
|
||||
&Thing{KV: "kv", NS: "ns", DB: "db", TB: "person", ID: 9223372036854775807},
|
||||
&Thing{KV: "kv", NS: "ns", DB: "db", TB: "person", ID: int64(9223372036854775807)},
|
||||
&Thing{KV: "kv", NS: "ns", DB: "db", TB: "person", ID: int64(math.MaxInt64)},
|
||||
&Thing{KV: "kv", NS: "ns", DB: "db", TB: "person", ID: "A"},
|
||||
&Thing{KV: "kv", NS: "ns", DB: "db", TB: "person", ID: "B"},
|
||||
&Thing{KV: "kv", NS: "ns", DB: "db", TB: "person", ID: "Bb"},
|
||||
|
@ -280,11 +285,11 @@ func TestMain(t *testing.T) {
|
|||
|
||||
&Index{KV: "kv", NS: "ns", DB: "db", TB: "person", IX: "names", FD: []interface{}{"account:zymba", 0, 127}},
|
||||
&Index{KV: "kv", NS: "ns", DB: "db", TB: "person", IX: "names", FD: []interface{}{"account:zymba", 0, 127}},
|
||||
&Index{KV: "kv", NS: "ns", DB: "db", TB: "person", IX: "names", FD: []interface{}{"account:zymba", 1, 127}},
|
||||
&Index{KV: "kv", NS: "ns", DB: "db", TB: "person", IX: "names", FD: []interface{}{"account:zymba", 2, 32767}},
|
||||
&Index{KV: "kv", NS: "ns", DB: "db", TB: "person", IX: "names", FD: []interface{}{"account:zymba", 2, 2147483647}},
|
||||
&Index{KV: "kv", NS: "ns", DB: "db", TB: "person", IX: "names", FD: []interface{}{"account:zymba", 2, 9223372036854775807}},
|
||||
&Index{KV: "kv", NS: "ns", DB: "db", TB: "person", IX: "names", FD: []interface{}{"account:zymba", 2, 9223372036854775807}},
|
||||
&Index{KV: "kv", NS: "ns", DB: "db", TB: "person", IX: "names", FD: []interface{}{"account:zymba", 1, math.MaxInt8}},
|
||||
&Index{KV: "kv", NS: "ns", DB: "db", TB: "person", IX: "names", FD: []interface{}{"account:zymba", 2, math.MaxInt16}},
|
||||
&Index{KV: "kv", NS: "ns", DB: "db", TB: "person", IX: "names", FD: []interface{}{"account:zymba", 2, math.MaxInt32}},
|
||||
&Index{KV: "kv", NS: "ns", DB: "db", TB: "person", IX: "names", FD: []interface{}{"account:zymba", 2, MaxNumber}},
|
||||
&Index{KV: "kv", NS: "ns", DB: "db", TB: "person", IX: "names", FD: []interface{}{"account:zymba", 2, MaxNumber}},
|
||||
|
||||
&Index{KV: "kv", NS: "ns", DB: "db", TB: "person", IX: "names", FD: Suffix},
|
||||
}
|
||||
|
@ -369,10 +374,10 @@ func TestEncoding(t *testing.T) {
|
|||
Convey(test.str, t, func() {
|
||||
|
||||
enc := test.obj.Encode()
|
||||
Printf("%s\n\n%#q\n\n%v\n\n", test.str, enc, enc)
|
||||
test.new.Decode(enc)
|
||||
|
||||
Convey("Key should encode and decode", func() {
|
||||
Printf("%s\n\n%#q\n\n%v\n\n", test.str, enc, enc)
|
||||
So(test.new, ShouldResemble, test.obj)
|
||||
})
|
||||
|
||||
|
@ -417,9 +422,8 @@ func TestSorting(t *testing.T) {
|
|||
one := sorts[i-1].Encode()
|
||||
two := sorts[i].Encode()
|
||||
|
||||
Printf("%#v\n%#v\n------\n%#v\n%#v\n------\n%#q\n%#q", sorts[i-1], sorts[i], one, two, one, two)
|
||||
|
||||
Convey("Key should sort before next key", func() {
|
||||
Printf("%#v\n%#v\n------\n%#v\n%#v\n------\n%#q\n%#q", sorts[i-1], sorts[i], one, two, one, two)
|
||||
So(string(one), ShouldBeLessThanOrEqualTo, string(two))
|
||||
})
|
||||
})
|
||||
|
|
|
@ -127,11 +127,16 @@ func (r *reader) FindString() (val string) {
|
|||
return
|
||||
}
|
||||
|
||||
func (r *reader) FindNumber() (val int64) {
|
||||
func (r *reader) FindNumber() (val float64) {
|
||||
if r.ReadNext(cNUMBER) {
|
||||
binary.Read(r.Reader, binary.BigEndian, &val)
|
||||
r.ReadNext(cTERM)
|
||||
return
|
||||
if r.ReadNext(cNILL) {
|
||||
binary.Read(r.Reader, binary.BigEndian, &val)
|
||||
val = 0 - val
|
||||
r.ReadNext(cTERM)
|
||||
} else if r.ReadNext(cBOOL) {
|
||||
binary.Read(r.Reader, binary.BigEndian, &val)
|
||||
r.ReadNext(cTERM)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -33,37 +33,99 @@ func newWriter(w io.Writer) *writer {
|
|||
func (w *writer) Write(i interface{}) {
|
||||
|
||||
switch v := i.(type) {
|
||||
|
||||
case []byte:
|
||||
w.Writer.Write(v)
|
||||
|
||||
case string:
|
||||
w.Writer.Write([]byte(v))
|
||||
case uint:
|
||||
binary.Write(w.Writer, binary.BigEndian, int64(v))
|
||||
case uint8:
|
||||
binary.Write(w.Writer, binary.BigEndian, int64(v))
|
||||
case uint16:
|
||||
binary.Write(w.Writer, binary.BigEndian, int64(v))
|
||||
case uint32:
|
||||
binary.Write(w.Writer, binary.BigEndian, int64(v))
|
||||
case uint64:
|
||||
binary.Write(w.Writer, binary.BigEndian, int64(v))
|
||||
case int:
|
||||
binary.Write(w.Writer, binary.BigEndian, int64(v))
|
||||
case int8:
|
||||
binary.Write(w.Writer, binary.BigEndian, int64(v))
|
||||
case int16:
|
||||
binary.Write(w.Writer, binary.BigEndian, int64(v))
|
||||
case int32:
|
||||
binary.Write(w.Writer, binary.BigEndian, int64(v))
|
||||
case int64:
|
||||
binary.Write(w.Writer, binary.BigEndian, int64(v))
|
||||
case float32:
|
||||
binary.Write(w.Writer, binary.BigEndian, int64(v))
|
||||
case float64:
|
||||
binary.Write(w.Writer, binary.BigEndian, int64(v))
|
||||
|
||||
case time.Time:
|
||||
binary.Write(w.Writer, binary.BigEndian, v.UnixNano())
|
||||
|
||||
case uint:
|
||||
w.Write(bBOOL)
|
||||
binary.Write(w.Writer, binary.BigEndian, float64(v))
|
||||
|
||||
case uint8:
|
||||
w.Write(bBOOL)
|
||||
binary.Write(w.Writer, binary.BigEndian, float64(v))
|
||||
|
||||
case uint16:
|
||||
w.Write(bBOOL)
|
||||
binary.Write(w.Writer, binary.BigEndian, float64(v))
|
||||
|
||||
case uint32:
|
||||
w.Write(bBOOL)
|
||||
binary.Write(w.Writer, binary.BigEndian, float64(v))
|
||||
|
||||
case uint64:
|
||||
w.Write(bBOOL)
|
||||
binary.Write(w.Writer, binary.BigEndian, float64(v))
|
||||
|
||||
case int:
|
||||
if v < 0 {
|
||||
w.Write(bNILL)
|
||||
binary.Write(w.Writer, binary.BigEndian, 0-float64(v))
|
||||
} else {
|
||||
w.Write(bBOOL)
|
||||
binary.Write(w.Writer, binary.BigEndian, float64(v))
|
||||
}
|
||||
|
||||
case int8:
|
||||
if v < 0 {
|
||||
w.Write(bNILL)
|
||||
binary.Write(w.Writer, binary.BigEndian, 0-float64(v))
|
||||
} else {
|
||||
w.Write(bBOOL)
|
||||
binary.Write(w.Writer, binary.BigEndian, float64(v))
|
||||
}
|
||||
|
||||
case int16:
|
||||
if v < 0 {
|
||||
w.Write(bNILL)
|
||||
binary.Write(w.Writer, binary.BigEndian, 0-float64(v))
|
||||
} else {
|
||||
w.Write(bBOOL)
|
||||
binary.Write(w.Writer, binary.BigEndian, float64(v))
|
||||
}
|
||||
|
||||
case int32:
|
||||
if v < 0 {
|
||||
w.Write(bNILL)
|
||||
binary.Write(w.Writer, binary.BigEndian, 0-float64(v))
|
||||
} else {
|
||||
w.Write(bBOOL)
|
||||
binary.Write(w.Writer, binary.BigEndian, float64(v))
|
||||
}
|
||||
|
||||
case int64:
|
||||
if v < 0 {
|
||||
w.Write(bNILL)
|
||||
binary.Write(w.Writer, binary.BigEndian, 0-float64(v))
|
||||
} else {
|
||||
w.Write(bBOOL)
|
||||
binary.Write(w.Writer, binary.BigEndian, float64(v))
|
||||
}
|
||||
|
||||
case float32:
|
||||
if v < 0 {
|
||||
w.Write(bNILL)
|
||||
binary.Write(w.Writer, binary.BigEndian, 0-float64(v))
|
||||
} else {
|
||||
w.Write(bBOOL)
|
||||
binary.Write(w.Writer, binary.BigEndian, float64(v))
|
||||
}
|
||||
|
||||
case float64:
|
||||
if v < 0 {
|
||||
w.Write(bNILL)
|
||||
binary.Write(w.Writer, binary.BigEndian, 0-float64(v))
|
||||
} else {
|
||||
w.Write(bBOOL)
|
||||
binary.Write(w.Writer, binary.BigEndian, float64(v))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue