diff --git a/little/little.go b/little/little.go index 7266796..e06f0a1 100644 --- a/little/little.go +++ b/little/little.go @@ -960,7 +960,7 @@ func ReadVarUint32(src io.Reader) (num uint32, err error) { // значащие. func WriteVarUint64(w io.Writer, num uint64) (int, error) { a := make([]byte, 9) - for i := 0; i < 8; i++ { + for i := range 8 { a[i] = byte(num & 127) num >>= 7 if num == 0 { @@ -977,7 +977,7 @@ func WriteVarUint64(w io.Writer, num uint64) (int, error) { // ReadVarUint64 - читает из io.Reader от 1 до 9 байт, и декодирует их в uint64. func ReadVarUint64(src io.Reader) (num uint64, err error) { var b byte - for i := 0; i < 8; i++ { + for i := range 8 { b, err = ReadByte(src) if err != nil { return @@ -1024,7 +1024,7 @@ func WriteVarSize(w io.Writer, n int) (int, error) { num = uint64(n) a = make([]byte, 9) ) - for i := 0; i < 8; i++ { + for i := range 8 { a[i] = byte(num & 127) num >>= 7 if num == 0 { @@ -1043,7 +1043,7 @@ func ReadVarSize(src io.Reader) (_ int, err error) { b byte num uint64 ) - for i := 0; i < 8; i++ { + for i := range 8 { b, err = ReadByte(src) if err != nil { return @@ -1098,6 +1098,46 @@ func CountVarUint64(n uint64) int { } return 9 } + +var varInt64PlusCutoffs = []int64{ + 1 << 6, + 1 << 13, + 1 << 20, + 1 << 27, + 1 << 34, + 1 << 41, + 1 << 48, + 1 << 55, +} + +var varInt64MinusCutoffs = []int64{ + -1 << 6, + -1 << 13, + -1 << 20, + -1 << 27, + -1 << 34, + -1 << 41, + -1 << 48, + -1 << 55, +} + +func CountVarInt64(n int64) int { + if n >= 0 { + for i, cutoff := range varInt64PlusCutoffs { + if n < cutoff { + return i + 1 + } + } + } else { + for i, cutoff := range varInt64MinusCutoffs { + if n >= cutoff { + return i + 1 + } + } + } + return 9 +} + func CountVarSize(n int) int { u64 := uint64(n) for i, cutoff := range varUint64Cutoffs { @@ -1110,15 +1150,9 @@ func CountVarSize(n int) int { // PUT/GET VAR SIZE -func PutVarSize(a []byte, n int) (int, error) { - if n < 0 { - return 0, ErrNegativeSize - } - var ( - num = uint64(n) - tmp = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0} - ) - for i := 0; i < 8; i++ { +func PutVarUint64(a []byte, num uint64) (int, error) { + var tmp [9]byte + for i := range 8 { if i < len(a) { if num < 128 { tmp[i] = byte(num) | 128 @@ -1136,26 +1170,191 @@ func PutVarSize(a []byte, n int) (int, error) { return 0, ErrNoSpace } tmp[8] = byte(num) - copy(a, tmp) + copy(a, tmp[:]) return 9, nil } -func GetVarSize(a []byte) (int, error) { +func GetVarUint64(a []byte) (uint64, int, error) { var num uint64 - for i := 0; i < 8; i++ { + for i := range 8 { if i < len(a) { b := a[i] num |= uint64(b&127) << uint(i*7) if b >= 128 { - return int(num), nil + return num, i + 1, nil } } else { - return 0, ErrNoSpace + return 0, 0, ErrNoSpace } } if len(a) < 9 { - return 0, ErrNoSpace + return 0, 0, ErrNoSpace } num |= uint64(a[8]) << 56 + return num, 9, nil +} + +func PutVarSize(arr []byte, n int) (int, error) { + if n < 0 { + return 0, ErrNegativeSize + } + return PutVarUint64(arr, uint64(n)) +} + +func GetVarSize(arr []byte) (int, error) { + num, _, err := GetVarUint64(arr) + if err != nil { + return 0, err + } return int(num), nil } + +func PutVarInt64(arr []byte, num int64) (int, error) { + return PutVarUint64(arr, EncodeZigZag(num)) +} + +func GetVarInt64(arr []byte) (int64, int, error) { + u64, n, err := GetVarUint64(arr) + if err != nil { + return 0, 0, err + } + return DecodeZigZag(u64), n, nil +} + +// REVERSE + +// func reverseCopy(dst []byte, src []byte) error { +// if len(dst) >= len(src) { +// i := len(dst) - 1 +// for _, b := range src { +// dst[i] = b +// i-- +// } +// return nil +// } +// return ErrNoSpace +// } + +func copyReversed(dst []byte, src []byte) error { + if len(dst) >= len(src) { + j := 0 + for i := len(src) - 1; i >= 0; i-- { + dst[j] = src[i] + j++ + } + return nil + } + return ErrNoSpace +} + +// ReversePutVarUint64 - записує закодоване значення як PutVarUint64, але із оберненим +// порядком байт. Прочитати значення можна функцією ReverseGetVarUint64, вказавши правильне +// початкове зміщення справа. +func ReversePutVarUint64(arr []byte, num uint64) (int, error) { + var tmp [9]byte + for i := range 8 { + tmp[i] = byte(num & 127) + num >>= 7 + if num == 0 { + tmp[i] |= 128 + n := i + 1 + err := copyReversed(arr, tmp[:n]) + if err != nil { + return 0, err + } + return n, nil + } + } + tmp[8] = byte(num) + err := copyReversed(arr, tmp[:]) + if err != nil { + return 0, err + } + return 9, nil +} + +// ReverseGetVarUint64 - читає значення, закодоване функцією ReversePutVarUint64. +// Останній байт arr - це молодший байт закодованного значення. +func ReverseGetVarUint64(arr []byte) (uint64, int, error) { + var ( + num uint64 + j = len(arr) - 1 + ) + for i := range 8 { + if j >= 0 { + b := arr[j] + num |= uint64(b&127) << uint(i*7) + if b >= 128 { + return num, i + 1, nil + } + j-- + } else { + return 0, 0, io.EOF + } + } + if j >= 0 { + num |= uint64(arr[j]) << 56 + return num, 9, nil + } + return 0, 0, io.EOF +} + +// ReversePutVarInt64 - записує закодоване значення як PutVarInt64, але із оберненим +// порядком байт. Прочитати значення можна функцією ReverseGetVarInt64, вказавши правильне +// початкове зміщення справа. +func ReversePutVarInt64(arr []byte, num int64) (int, error) { + return ReversePutVarUint64(arr, EncodeZigZag(num)) +} + +// ReverseGetVarInt64 - читає значення, закодоване функцією ReversePutVarInt64. +// Останній байт arr - це молодший байт закодованного значення. +func ReverseGetVarInt64(arr []byte) (int64, int, error) { + u64, n, err := ReverseGetVarUint64(arr) + if err != nil { + return 0, 0, err + } + return DecodeZigZag(u64), n, nil +} + +// TailPutVarUint64 - записує закодоване значення в кінці arr. +// Прочитати значення можна функцією GetVarUint64, вказавши правильне +// початкове зміщення зліва. +func TailPutVarUint64(arr []byte, num uint64) (int, error) { + var tmp [9]byte + for i := range 8 { + tmp[i] = byte(num & 127) + num >>= 7 + if num == 0 { + tmp[i] |= 128 + n := i + 1 + if len(arr) < n { + return 0, ErrNoSpace + } + copy(arr[len(arr)-n:], tmp[:n]) + return n, nil + } + } + tmp[8] = byte(num) + if len(arr) < 9 { + return 0, ErrNoSpace + } + copy(arr[len(arr)-9:], tmp[:]) + return 9, nil +} + +// TailPutVarInt64 - записує закодоване значення в кінці arr. +// Прочитати значення можна функцією GetVarInt64, вказавши правильне +// початкове зміщення зліва. +func TailPutVarInt64(arr []byte, num int64) (int, error) { + return TailPutVarUint64(arr, EncodeZigZag(num)) +} + +// ZigZag encoding: int64 -> uint64 +func EncodeZigZag(x int64) uint64 { + return uint64(x<<1) ^ uint64(x>>63) +} + +// ZigZag decoding: uint64 -> int64 +func DecodeZigZag(u uint64) int64 { + return int64(u>>1) ^ -(int64(u & 1)) +} diff --git a/little/little_test.go b/little/little_test.go index c092359..d90388c 100644 --- a/little/little_test.go +++ b/little/little_test.go @@ -703,24 +703,25 @@ func TestWriteReadBool(t *testing.T) { } } -// VAR SIZE COUNT +// COUNT VAR SIZE + +var varUint32TestCases = []struct { + Num uint32 + Count int +}{ + {Num: (1 << 7) - 1, Count: 1}, + {Num: (1 << 7), Count: 2}, + {Num: (1 << 14) - 1, Count: 2}, + {Num: (1 << 14), Count: 3}, + {Num: (1 << 21) - 1, Count: 3}, + {Num: (1 << 21), Count: 4}, + {Num: (1 << 28) - 1, Count: 4}, + {Num: (1 << 28), Count: 5}, + {Num: (1 << 32) - 1, Count: 5}, +} func TestCountVarUint32(t *testing.T) { - var testCases = []struct { - Num uint32 - Count int - }{ - {Num: (1 << 7) - 1, Count: 1}, - {Num: (1 << 7), Count: 2}, - {Num: (1 << 14) - 1, Count: 2}, - {Num: (1 << 14), Count: 3}, - {Num: (1 << 21) - 1, Count: 3}, - {Num: (1 << 21), Count: 4}, - {Num: (1 << 28) - 1, Count: 4}, - {Num: (1 << 28), Count: 5}, - {Num: (1 << 32) - 1, Count: 5}, - } - for _, testCase := range testCases { + for _, testCase := range varUint32TestCases { count := CountVarUint32(testCase.Num) if count != testCase.Count { t.Fatalf("calculated count %d is not equal %d for num %d\n", @@ -729,30 +730,33 @@ func TestCountVarUint32(t *testing.T) { } } +var varUint64TestCases = []struct { + Num uint64 + Count int +}{ + {Num: 0, Count: 1}, + {Num: 1, Count: 1}, + {Num: (1 << 7) - 1, Count: 1}, + {Num: (1 << 7), Count: 2}, + {Num: (1 << 14) - 1, Count: 2}, + {Num: (1 << 14), Count: 3}, + {Num: (1 << 21) - 1, Count: 3}, + {Num: (1 << 21), Count: 4}, + {Num: (1 << 28) - 1, Count: 4}, + {Num: (1 << 28), Count: 5}, + {Num: (1 << 35) - 1, Count: 5}, + {Num: (1 << 35), Count: 6}, + {Num: (1 << 42) - 1, Count: 6}, + {Num: (1 << 42), Count: 7}, + {Num: (1 << 49) - 1, Count: 7}, + {Num: (1 << 49), Count: 8}, + {Num: (1 << 56) - 1, Count: 8}, + {Num: (1 << 56), Count: 9}, + {Num: (1 << 64) - 1, Count: 9}, +} + func TestCountVarUint64(t *testing.T) { - var testCases = []struct { - Num uint64 - Count int - }{ - {Num: (1 << 7) - 1, Count: 1}, - {Num: (1 << 7), Count: 2}, - {Num: (1 << 14) - 1, Count: 2}, - {Num: (1 << 14), Count: 3}, - {Num: (1 << 21) - 1, Count: 3}, - {Num: (1 << 21), Count: 4}, - {Num: (1 << 28) - 1, Count: 4}, - {Num: (1 << 28), Count: 5}, - {Num: (1 << 35) - 1, Count: 5}, - {Num: (1 << 35), Count: 6}, - {Num: (1 << 42) - 1, Count: 6}, - {Num: (1 << 42), Count: 7}, - {Num: (1 << 49) - 1, Count: 7}, - {Num: (1 << 49), Count: 8}, - {Num: (1 << 56) - 1, Count: 8}, - {Num: (1 << 56), Count: 9}, - {Num: (1 << 64) - 1, Count: 9}, - } - for _, testCase := range testCases { + for _, testCase := range varUint64TestCases { count := CountVarUint64(testCase.Num) if count != testCase.Count { t.Fatalf("calculated count %d is not equal %d for num %d\n", @@ -761,10 +765,65 @@ func TestCountVarUint64(t *testing.T) { } } +var varInt64TestCases = []struct { + Num int64 + Count int +}{ + {Num: -(1 << 63), Count: 9}, + {Num: -(1 << 55) - 1, Count: 9}, + {Num: -(1 << 55), Count: 8}, + {Num: -(1 << 48) - 1, Count: 8}, + {Num: -(1 << 48), Count: 7}, + {Num: -(1 << 41) - 1, Count: 7}, + {Num: -(1 << 41), Count: 6}, + {Num: -(1 << 34) - 1, Count: 6}, + {Num: -(1 << 34), Count: 5}, + {Num: -(1 << 27) - 1, Count: 5}, + {Num: -(1 << 27), Count: 4}, + {Num: -(1 << 20) - 1, Count: 4}, + {Num: -(1 << 20), Count: 3}, + {Num: -(1 << 13) - 1, Count: 3}, + {Num: -(1 << 13), Count: 2}, + {Num: -(1 << 6) - 1, Count: 2}, + {Num: -(1 << 6), Count: 1}, + {Num: -1, Count: 1}, + {Num: 0, Count: 1}, + {Num: 1, Count: 1}, + {Num: (1 << 6) - 1, Count: 1}, + {Num: (1 << 6), Count: 2}, + {Num: (1 << 13) - 1, Count: 2}, + {Num: (1 << 13), Count: 3}, + {Num: (1 << 20) - 1, Count: 3}, + {Num: (1 << 20), Count: 4}, + {Num: (1 << 27) - 1, Count: 4}, + {Num: (1 << 27), Count: 5}, + {Num: (1 << 34) - 1, Count: 5}, + {Num: (1 << 34), Count: 6}, + {Num: (1 << 41) - 1, Count: 6}, + {Num: (1 << 41), Count: 7}, + {Num: (1 << 48) - 1, Count: 7}, + {Num: (1 << 48), Count: 8}, + {Num: (1 << 55) - 1, Count: 8}, + {Num: (1 << 55), Count: 9}, + {Num: (1 << 63) - 1, Count: 9}, +} + +func TestCountVarInt64(t *testing.T) { + for _, testCase := range varInt64TestCases { + count := CountVarInt64(testCase.Num) + if count != testCase.Count { + t.Fatalf("calculated count %d is not equal %d for num %d\n", + count, testCase.Count, testCase.Num) + } + } +} + var varSizeTestCases = []struct { Num int Count int }{ + {Num: 0, Count: 1}, + {Num: 1, Count: 1}, {Num: (1 << 7) - 1, Count: 1}, {Num: (1 << 7), Count: 2}, {Num: (1 << 14) - 1, Count: 2}, @@ -794,6 +853,62 @@ func TestCountVarSize(t *testing.T) { } } +// PUT & GET VAR INT64 + +func TestPutGetVarInt64(t *testing.T) { + for _, testCase := range varInt64TestCases { + a := make([]byte, 9) + putCount, err := PutVarInt64(a, testCase.Num) + if err != nil { + t.Fatalf("put num %d: %s", testCase.Num, err) + } + decoded, getCount, err := GetVarInt64(a) + if err != nil { + t.Fatalf("get num %d: %s", testCase.Num, err) + } + if decoded != testCase.Num { + t.Fatalf("num %d not equal decoded %d", testCase.Num, decoded) + } + if putCount != testCase.Count { + t.Fatalf("count %d not equal put count %d of num %d", + testCase.Count, putCount, testCase.Num) + } + if getCount != testCase.Count { + t.Fatalf("count %d not equal get count %d of num %d", + testCase.Count, getCount, testCase.Num) + } + } +} + +// PUT & GET VAR UINT64 + +func TestPutGetVarUint64(t *testing.T) { + for _, testCase := range varUint64TestCases { + a := make([]byte, 9) + putCount, err := PutVarUint64(a, testCase.Num) + if err != nil { + t.Fatalf("put %d: %s\n", testCase.Num, err) + } + decoded, getCount, err := GetVarUint64(a) + if err != nil { + t.Fatalf("get %d: %s\n", testCase.Num, err) + } + if decoded != testCase.Num { + t.Fatalf("num %d not equal decoded %d", testCase.Num, decoded) + } + if putCount != testCase.Count { + t.Fatalf("count %d not equal put count %d of num %d", + testCase.Count, putCount, testCase.Num) + } + if getCount != testCase.Count { + t.Fatalf("count %d not equal get count %d of num %d", + testCase.Count, getCount, testCase.Num) + } + } +} + +// WRITE & READ VAR SIZE + func TestWriteReadVarSize(t *testing.T) { for _, testCase := range varSizeTestCases { w := bytes.NewBuffer(nil) @@ -821,6 +936,8 @@ func TestWriteReadVarSize(t *testing.T) { } } +// PUT & GET VAR SIZE + func TestPutGetVarSize(t *testing.T) { for _, testCase := range varSizeTestCases { a := make([]byte, 9) @@ -848,6 +965,110 @@ func TestPutGetVarSize(t *testing.T) { } } +// PUT & GET REVERSE VAR UINT64 / INT64 + +func TestReversePutGetVarUint64(t *testing.T) { + for _, testCase := range varUint64TestCases { + a := make([]byte, 9) + putCount, err := ReversePutVarUint64(a, testCase.Num) + if err != nil { + t.Fatalf("put num %d: %s", testCase.Num, err) + } + decoded, getCount, err := ReverseGetVarUint64(a[:testCase.Count]) + if err != nil { + t.Fatalf("get num %d: %s", testCase.Num, err) + } + if decoded != testCase.Num { + t.Fatalf("num %d not equal decoded %d", testCase.Num, decoded) + } + if putCount != testCase.Count { + t.Fatalf("count %d not equal put count %d of num %d", + testCase.Count, putCount, testCase.Num) + } + if getCount != testCase.Count { + t.Fatalf("count %d not equal get count %d of num %d", + testCase.Count, getCount, testCase.Num) + } + } +} + +func TestReversePutGetVarInt64(t *testing.T) { + for _, testCase := range varInt64TestCases { + a := make([]byte, 9) + putCount, err := ReversePutVarInt64(a, testCase.Num) + if err != nil { + t.Fatalf("put num %d: %s", testCase.Num, err) + } + decoded, getCount, err := ReverseGetVarInt64(a[:testCase.Count]) + if err != nil { + t.Fatalf("get num %d: %s", testCase.Num, err) + } + if decoded != testCase.Num { + t.Fatalf("num %d not equal decoded %d", testCase.Num, decoded) + } + if putCount != testCase.Count { + t.Fatalf("count %d not equal put count %d of num %d", + testCase.Count, putCount, testCase.Num) + } + if getCount != testCase.Count { + t.Fatalf("count %d not equal get count %d of num %d", + testCase.Count, getCount, testCase.Num) + } + } +} + +// PUT TAIL VAR UINT64/INT64 & GET VAR UINT64/INT64 + +func TestTailPutGetVarInt64(t *testing.T) { + for _, testCase := range varInt64TestCases { + a := make([]byte, 9) + putCount, err := TailPutVarInt64(a, testCase.Num) + if err != nil { + t.Fatalf("put num %d: %s", testCase.Num, err) + } + decoded, getCount, err := GetVarInt64(a[9-testCase.Count:]) + if err != nil { + t.Fatalf("get num %d: %s", testCase.Num, err) + } + if decoded != testCase.Num { + t.Fatalf("num %d not equal decoded %d", testCase.Num, decoded) + } + if putCount != testCase.Count { + t.Fatalf("count %d not equal put count %d of num %d", + testCase.Count, putCount, testCase.Num) + } + if getCount != testCase.Count { + t.Fatalf("count %d not equal get count %d of num %d", + testCase.Count, getCount, testCase.Num) + } + } +} + +func TestTailPutGetVarUint64(t *testing.T) { + for _, testCase := range varUint64TestCases { + a := make([]byte, 9) + putCount, err := TailPutVarUint64(a, testCase.Num) + if err != nil { + t.Fatalf("put num %d: %s", testCase.Num, err) + } + decoded, getCount, err := GetVarUint64(a[9-testCase.Count:]) + if err != nil { + t.Fatalf("get num %d: %s", testCase.Num, err) + } + if decoded != testCase.Num { + t.Fatalf("num %d not equal decoded %d", testCase.Num, decoded) + } + if putCount != testCase.Count { + t.Fatalf("count %d not equal put count %d of num %d", + testCase.Count, putCount, testCase.Num) + } + if getCount != testCase.Count { + t.Fatalf("count %d not equal get count %d of num %d", + testCase.Count, getCount, testCase.Num) + } + } +} + // HELPERS const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"