This repository has been archived by the owner on Feb 1, 2024. It is now read-only.
/
number.go
201 lines (169 loc) · 5.66 KB
/
number.go
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
package model
import (
"fmt"
"log"
"math"
"strconv"
"github.com/stellar/go/price"
)
// NumberConstants holds some useful constants
var NumberConstants = struct {
Zero *Number
One *Number
}{
Zero: NumberFromFloat(0.0, 16),
One: NumberFromFloat(1.0, 16),
}
// InvertPrecision is the precision of the number after it is inverted
const InvertPrecision = 15
// InternalCalculationsPrecision is the precision to be used for internal calculations in a function
const InternalCalculationsPrecision = 15
// Number abstraction
type Number struct {
value float64
precision int8
}
// AsFloat gives a float64 representation
func (n Number) AsFloat() float64 {
return n.value
}
// Precision gives the precision of the Number
func (n Number) Precision() int8 {
return n.precision
}
// AsString gives a string representation
func (n Number) AsString() string {
return fmt.Sprintf(fmt.Sprintf("%%.%df", n.Precision()), n.AsFloat())
}
// AsRatio returns an integer numerator and denominator
func (n Number) AsRatio() (int32, int32, error) {
p, e := price.Parse(n.AsString())
if e != nil {
return 0, 0, fmt.Errorf("unable to convert number to ratio: %s", e)
}
return int32(p.N), int32(p.D), nil
}
// Abs returns the absolute of the number
func (n Number) Abs() *Number {
if n.AsFloat() < 0 {
return n.Negate()
}
return &n
}
// Negate returns the negative value of the number
func (n Number) Negate() *Number {
return NumberConstants.Zero.Subtract(n)
}
// Add returns a new Number after adding the passed in Number
func (n Number) Add(n2 Number) *Number {
newPrecision := minPrecision(n, n2)
return NumberFromFloat(n.AsFloat()+n2.AsFloat(), newPrecision)
}
// Subtract returns a new Number after subtracting out the passed in Number
func (n Number) Subtract(n2 Number) *Number {
newPrecision := minPrecision(n, n2)
return NumberFromFloat(n.AsFloat()-n2.AsFloat(), newPrecision)
}
// Multiply returns a new Number after multiplying with the passed in Number by rounding up based on the smaller precision
func (n Number) Multiply(n2 Number) *Number {
newPrecision := minPrecision(n, n2)
return NumberFromFloat(n.AsFloat()*n2.AsFloat(), newPrecision)
}
// MultiplyRoundTruncate returns a new Number after multiplying with the passed in Number by truncating based on the smaller precision
func (n Number) MultiplyRoundTruncate(n2 Number) *Number {
newPrecision := minPrecision(n, n2)
return NumberFromFloatRoundTruncate(n.AsFloat()*n2.AsFloat(), newPrecision)
}
// Divide returns a new Number after dividing by the passed in Number by rounding up based on the smaller precision
func (n Number) Divide(n2 Number) *Number {
newPrecision := minPrecision(n, n2)
return NumberFromFloat(n.AsFloat()/n2.AsFloat(), newPrecision)
}
// DivideRoundTruncate returns a new Number after dividing by the passed in Number by truncating based on the smaller precision
func (n Number) DivideRoundTruncate(n2 Number) *Number {
newPrecision := minPrecision(n, n2)
return NumberFromFloatRoundTruncate(n.AsFloat()/n2.AsFloat(), newPrecision)
}
// Scale takes in a scalar with which to multiply the number using the same precision of the original number
func (n Number) Scale(scaleFactor float64) *Number {
return NumberFromFloat(n.AsFloat()*scaleFactor, n.precision)
}
// EqualsPrecisionNormalized returns true if the two numbers are the same after comparing them at the same (lowest) precision level
func (n Number) EqualsPrecisionNormalized(n2 Number, epsilon float64) bool {
return n.Subtract(n2).Abs().AsFloat() < epsilon
}
// String is the Stringer interface impl.
func (n Number) String() string {
return n.AsString()
}
// NumberFromFloat makes a Number from a float by rounding up
func NumberFromFloat(f float64, precision int8) *Number {
return &Number{
value: toFixed(f, precision, RoundUp),
precision: precision,
}
}
// NumberFromFloatRoundTruncate makes a Number from a float by truncating beyond the specified precision
func NumberFromFloatRoundTruncate(f float64, precision int8) *Number {
return &Number{
value: toFixed(f, precision, RoundTruncate),
precision: precision,
}
}
// NumberFromString makes a Number from a string, by calling NumberFromFloat
func NumberFromString(s string, precision int8) (*Number, error) {
parsed, e := strconv.ParseFloat(s, 64)
if e != nil {
return nil, e
}
return NumberFromFloat(parsed, precision), nil
}
// MustNumberFromString panics when there's an error
func MustNumberFromString(s string, precision int8) *Number {
parsed, e := NumberFromString(s, precision)
if e != nil {
log.Fatal(e)
}
return parsed
}
// InvertNumber inverts a number, returns nil if the original number is nil, preserves precision
func InvertNumber(n *Number) *Number {
if n == nil {
return nil
}
return NumberFromFloat(1.0/n.AsFloat(), InvertPrecision)
}
// NumberByCappingPrecision returns a number with a precision that is at max the passed in precision
func NumberByCappingPrecision(n *Number, precision int8) *Number {
if n.Precision() > precision {
return NumberFromFloat(n.AsFloat(), precision)
}
return n
}
func round(num float64, rounding Rounding) int64 {
if rounding == RoundUp {
return int64(num + math.Copysign(0.5, num))
} else if rounding == RoundTruncate {
return int64(num)
} else {
// error
return -1
}
}
// Rounding is a type that defines various approaching to rounding numbers
type Rounding int
// Rounding types
const (
RoundUp Rounding = iota
RoundTruncate
)
func toFixed(num float64, precision int8, rounding Rounding) float64 {
output := math.Pow(10, float64(precision))
return float64(round(num*output, rounding)) / output
}
func minPrecision(n1 Number, n2 Number) int8 {
if n1.precision < n2.precision {
return n1.precision
}
return n2.precision
}