-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathdecimal.go
More file actions
156 lines (124 loc) · 3.13 KB
/
decimal.go
File metadata and controls
156 lines (124 loc) · 3.13 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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package channels
import (
"bytes"
"fmt"
"math"
"github.com/tokenized/pkg/wire"
"github.com/pkg/errors"
)
var (
ErrInvalidCharacter = errors.New("Invalid Character")
ErrTooManyDecimals = errors.New("Too Many Decimals")
)
// Decimal represents a decimal number. It is designed to not have precision rounding errors for
// prices. "value" is the integer value the represents the full value on both sides of the decimal
// point. "precision" is the number of base 10 digits of "value" on the right side of the decimal
// point.
// TODO Implement multiplication, division, addition, and subtraction. --ce
type Decimal struct {
value uint64
precision uint8
}
func NewDecimal(value uint64, precision uint8) Decimal {
return Decimal{
value: value,
precision: precision,
}
}
func (d *Decimal) Set(value uint64, precision uint8) {
d.value = value
d.precision = precision
}
func (d Decimal) Equal(other Decimal) bool {
// TODO Add adjustment of different precisions with the same value. --ce
if d.value != other.value {
return false
}
if d.precision != other.precision {
return false
}
return true
}
func (d Decimal) String() string {
if d.precision == 0 {
return fmt.Sprintf("%d", d.value)
}
formatter := fmt.Sprintf("%%0%dd", d.precision+1)
s := fmt.Sprintf(formatter, d.value)
i := uint(len(s)) - uint(d.precision)
return s[:i] + "." + s[i:]
}
func (d *Decimal) SetString(s string) error {
var value uint64
var precision uint8
decimalFound := false
l := len(s)
for i := 0; i < l; i++ {
c := s[i]
if c == '.' {
if decimalFound {
return ErrTooManyDecimals
}
precision = uint8(l - i - 1)
continue
}
if c > '9' || c < '0' {
return errors.Wrap(ErrInvalidCharacter, string([]byte{c}))
}
v := uint64(c - '0')
value *= 10
value += v
}
d.value = value
d.precision = precision
return nil
}
func (d Decimal) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("\"%s\"", d)), nil
}
func (d *Decimal) UnmarshalJSON(data []byte) error {
l := len(data)
if l < 2 {
return errors.New("Missing Quotes")
}
if data[0] != '"' || data[l-1] != '"' {
return errors.New("Missing Quotes")
}
if err := d.SetString(string(data[1 : l-1])); err != nil {
return err
}
return nil
}
func (d Decimal) MarshalText() ([]byte, error) {
return []byte(d.String()), nil
}
func (d *Decimal) UnmarshalText(text []byte) error {
return d.SetString(string(text))
}
func (d Decimal) MarshalBinary() ([]byte, error) {
buf := &bytes.Buffer{}
if err := wire.WriteVarInt(buf, 0, d.value); err != nil {
return nil, errors.Wrap(err, "value")
}
if err := wire.WriteVarInt(buf, 0, uint64(d.precision)); err != nil {
return nil, errors.Wrap(err, "precision")
}
return buf.Bytes(), nil
}
func (d *Decimal) UnmarshalBinary(data []byte) error {
r := bytes.NewReader(data)
value, err := wire.ReadVarInt(r, 0)
if err != nil {
return errors.Wrap(err, "value")
}
precision, err := wire.ReadVarInt(r, 0)
if err != nil {
return errors.Wrap(err, "precision")
}
if precision > math.MaxUint8 {
return fmt.Errorf("Overloaded precision : %d", precision)
}
d.value = value
d.precision = uint8(precision)
return nil
}