-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathnumbers.go
More file actions
125 lines (108 loc) · 2.83 KB
/
numbers.go
File metadata and controls
125 lines (108 loc) · 2.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package bogo
import (
"encoding/binary"
"fmt"
"math"
)
func encodeNum(v any) ([]byte, error) {
switch data := v.(type) {
case int:
return encodeInt(int64(data))
case int8:
return encodeInt(int64(data))
case int16:
return encodeInt(int64(data))
case int32:
return encodeInt(int64(data))
case int64:
return encodeInt(data)
case uint:
return encodeUint(uint64(data))
case uint8:
return encodeUint(uint64(data))
case uint16:
return encodeUint(uint64(data))
case uint32:
return encodeUint(uint64(data))
case uint64:
return encodeUint(uint64(data))
case float32:
return encodeFloat(float64(data))
case float64:
return encodeFloat(data)
default:
return nil, fmt.Errorf("unsupported numeric type: %T", v)
}
}
// todo: this path is used in all encoding paths. oportunity to optimize.
// check profiles to confirm, allocs per op
func encodeUint(data uint64) ([]byte, error) {
result := make([]byte, binary.MaxVarintLen64+2)
result[0] = byte(TypeUint)
n := binary.PutUvarint(result[2:], data)
result[1] = byte(n)
return result[:n+2], nil
}
func encodeInt(data int64) ([]byte, error) {
result := make([]byte, binary.MaxVarintLen64+2)
result[0] = byte(TypeInt)
n := binary.PutVarint(result[2:], data)
result[1] = byte(n)
return result[:n+2], nil
}
func decodeInt(data []byte) (int64, error) {
val, n := binary.Varint(data)
if n <= 0 {
return 0, fmt.Errorf("failed to decode int at line %d", 119)
}
return val, nil
}
func decodeUint(data []byte) (uint64, error) {
val, n := binary.Uvarint(data)
if n <= 0 {
return 0, fmt.Errorf("failed to decode uint at line %d", 126)
}
return val, nil
}
func decomposeFloat64(f float64) (signExp uint16, mantissa uint64) {
bits := math.Float64bits(f)
sign := int(bits >> 63)
exponent := uint16((bits >> 52) & 0x7FF)
mantissa = bits & 0xFFFFFFFFFFFFF
// pack sign and exponent into 2 bytes
signExp = (uint16(sign) << 15) | (exponent & 0x7FFF)
return
}
func encodeFloat(f float64) ([]byte, error) {
signExp, mant := decomposeFloat64(f)
buf := make([]byte, 32) // big enough buffer
buf[0] = byte(TypeFloat)
binary.LittleEndian.PutUint16(buf[2:4], signExp) // write 2 bytes
// encode mantisa
n := 0
if mant != 0 {
n = binary.PutUvarint(buf[4:], mant)
}
buf[1] = byte(n + 2)
return buf[:n+4], nil
}
// todo: use a different encoding scheme that does not loose precision
func decodeFloat(data []byte) (float64, error) {
if len(data) == 0 {
return 0, fmt.Errorf("empty input")
}
signExpo := binary.LittleEndian.Uint16(data[0:2])
sign := int(signExpo >> 15)
exp := signExpo & 0x7FFF
mantissa := uint64(0)
if len(data) > 2 {
n := 0
mantissa, n = binary.Uvarint(data[2:])
if n <= 0 {
return 0, fmt.Errorf("failed to decode mantissa")
}
}
// Rebuild float64 bits
bits := (uint64(sign) << 63) | (uint64(exp) << 52) | mantissa
return math.Float64frombits(bits), nil
}