forked from evmos/cosmos-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
/
rational.go
237 lines (210 loc) · 8.04 KB
/
rational.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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
package types
import (
"fmt"
"math/big"
"strconv"
"strings"
)
// "that's one big rat!"
// ______
// / / /\ \____oo
// __ /___...._____ _\o
// __| |_ |_
// Rat - extend big.Rat
// NOTE: never use new(Rat) or else
// we will panic unmarshalling into the
// nil embedded big.Rat
type Rat struct {
Num int64 `json:"num"`
Denom int64 `json:"denom"`
//*big.Rat `json:"rat"`
}
// RatInterface - big Rat with additional functionality
// NOTE: we only have one implementation of this interface
// and don't use it anywhere, but it might come in handy
// if we want to provide Rat types that include
// the units of the value in the type system.
//type RatInterface interface {
//GetRat() *big.Rat
//Num() int64
//Denom() int64
//GT(Rat) bool
//LT(Rat) bool
//Equal(Rat) bool
//IsZero() bool
//Inv() Rat
//Mul(Rat) Rat
//Quo(Rat) Rat
//Add(Rat) Rat
//Sub(Rat) Rat
//Round(int64) Rat
//Evaluate() int64
//}
//var _ Rat = Rat{} // enforce at compile time
// nolint - common values
var (
ZeroRat = NewRat(0) // Rat{big.NewRat(0, 1)}
OneRat = NewRat(1) // Rat{big.NewRat(1, 1)}
)
// New - create a new Rat from integers
//func NewRat(Numerator int64, Denominator ...int64) Rat {
//switch len(Denominator) {
//case 0:
//return Rat{big.NewRat(Numerator, 1)}
//case 1:
//return Rat{big.NewRat(Numerator, Denominator[0])}
//default:
//panic("improper use of New, can only have one denominator")
//}
//}
func NewRat(num int64, denom ...int64) Rat {
switch len(denom) {
case 0:
return Rat{
Num: num,
Denom: 1,
}
case 1:
return Rat{
Num: num,
Denom: denom[0],
}
default:
panic("improper use of New, can only have one denominator")
}
}
// create a rational from decimal string or integer string
func NewRatFromDecimal(decimalStr string) (f Rat, err Error) {
// first extract any negative symbol
neg := false
if string(decimalStr[0]) == "-" {
neg = true
decimalStr = decimalStr[1:]
}
str := strings.Split(decimalStr, ".")
var numStr string
var denom int64 = 1
switch len(str) {
case 1:
if len(str[0]) == 0 {
return f, NewError(CodeUnknownRequest, "not a decimal string")
}
numStr = str[0]
case 2:
if len(str[0]) == 0 || len(str[1]) == 0 {
return f, NewError(CodeUnknownRequest, "not a decimal string")
}
numStr = str[0] + str[1]
len := int64(len(str[1]))
denom = new(big.Int).Exp(big.NewInt(10), big.NewInt(len), nil).Int64()
default:
return f, NewError(CodeUnknownRequest, "not a decimal string")
}
num, errConv := strconv.Atoi(numStr)
if errConv != nil {
return f, NewError(CodeUnknownRequest, errConv.Error())
}
if neg {
num *= -1
}
return NewRat(int64(num), denom), nil
}
//nolint
func ToRat(r *big.Rat) Rat { return NewRat(r.Num().Int64(), r.Denom().Int64()) } // GetRat - get big.Rat
func (r Rat) GetRat() *big.Rat { return big.NewRat(r.Num, r.Denom) } // GetRat - get big.Rat
func (r Rat) IsZero() bool { return r.Num == 0 } // IsZero - Is the Rat equal to zero
func (r Rat) Equal(r2 Rat) bool { return r.GetRat().Cmp(r2.GetRat()) == 0 } // Equal - rationals are equal
func (r Rat) GT(r2 Rat) bool { return r.GetRat().Cmp(r2.GetRat()) == 1 } // GT - greater than
func (r Rat) LT(r2 Rat) bool { return r.GetRat().Cmp(r2.GetRat()) == -1 } // LT - less than
func (r Rat) Inv() Rat { return ToRat(new(big.Rat).Inv(r.GetRat())) } // Inv - inverse
func (r Rat) Mul(r2 Rat) Rat { return ToRat(new(big.Rat).Mul(r.GetRat(), r2.GetRat())) } // Mul - multiplication
func (r Rat) Quo(r2 Rat) Rat { return ToRat(new(big.Rat).Quo(r.GetRat(), r2.GetRat())) } // Quo - quotient
func (r Rat) Add(r2 Rat) Rat { return ToRat(new(big.Rat).Add(r.GetRat(), r2.GetRat())) } // Add - addition
func (r Rat) Sub(r2 Rat) Rat { return ToRat(new(big.Rat).Sub(r.GetRat(), r2.GetRat())) } // Sub - subtraction
//func (r Rat) GetRat() *big.Rat { return r.Rat } // GetRat - get big.Rat
//func (r Rat) Num() int64 { return r.Rat.Num().Int64() } // Num - return the numerator
//func (r Rat) Denom() int64 { return r.Rat.Denom().Int64() } // Denom - return the denominator
//func (r Rat) IsZero() bool { return r.Num() == 0 } // IsZero - Is the Rat equal to zero
//func (r Rat) Equal(r2 Rat) bool { return r.Rat.Cmp(r2.GetRat()) == 0 } // Equal - rationals are equal
//func (r Rat) GT(r2 Rat) bool { return r.Rat.Cmp(r2.GetRat()) == 1 } // GT - greater than
//func (r Rat) LT(r2 Rat) bool { return r.Rat.Cmp(r2.GetRat()) == -1 } // LT - less than
//func (r Rat) Inv() Rat { return Rat{new(big.Rat).Inv(r.Rat)} } // Inv - inverse
//func (r Rat) Mul(r2 Rat) Rat { return Rat{new(big.Rat).Mul(r.Rat, r2.GetRat())} } // Mul - multiplication
//func (r Rat) Quo(r2 Rat) Rat { return Rat{new(big.Rat).Quo(r.Rat, r2.GetRat())} } // Quo - quotient
//func (r Rat) Add(r2 Rat) Rat { return Rat{new(big.Rat).Add(r.Rat, r2.GetRat())} } // Add - addition
//func (r Rat) Sub(r2 Rat) Rat { return Rat{new(big.Rat).Sub(r.Rat, r2.GetRat())} } // Sub - subtraction
//func (r Rat) String() string { return fmt.Sprintf("%v/%v", r.Num(), r.Denom()) } // Sub - subtraction
var (
zero = big.NewInt(0)
one = big.NewInt(1)
two = big.NewInt(2)
five = big.NewInt(5)
nFive = big.NewInt(-5)
ten = big.NewInt(10)
)
// evaluate the rational using bankers rounding
func (r Rat) EvaluateBig() *big.Int {
num := r.GetRat().Num()
denom := r.GetRat().Denom()
d, rem := new(big.Int), new(big.Int)
d.QuoRem(num, denom, rem)
if rem.Cmp(zero) == 0 { // is the remainder zero
return d
}
// evaluate the remainder using bankers rounding
tenNum := new(big.Int).Mul(num, ten)
tenD := new(big.Int).Mul(d, ten)
remainderDigit := new(big.Int).Sub(new(big.Int).Quo(tenNum, denom), tenD) // get the first remainder digit
isFinalDigit := (new(big.Int).Rem(tenNum, denom).Cmp(zero) == 0) // is this the final digit in the remainder?
switch {
case isFinalDigit && (remainderDigit.Cmp(five) == 0 || remainderDigit.Cmp(nFive) == 0):
dRem2 := new(big.Int).Rem(d, two)
return new(big.Int).Add(d, dRem2) // always rounds to the even number
case remainderDigit.Cmp(five) != -1: //remainderDigit >= 5:
d.Add(d, one)
case remainderDigit.Cmp(nFive) != 1: //remainderDigit <= -5:
d.Sub(d, one)
}
return d
}
// evaluate the rational using bankers rounding
func (r Rat) Evaluate() int64 {
return r.EvaluateBig().Int64()
}
// round Rat with the provided precisionFactor
func (r Rat) Round(precisionFactor int64) Rat {
rTen := ToRat(new(big.Rat).Mul(r.GetRat(), big.NewRat(precisionFactor, 1)))
return ToRat(big.NewRat(rTen.Evaluate(), precisionFactor))
}
// TODO panic if negative or if totalDigits < len(initStr)???
// evaluate as an integer and return left padded string
func (r Rat) ToLeftPadded(totalDigits int8) string {
intStr := r.EvaluateBig().String()
fcode := `%0` + strconv.Itoa(int(totalDigits)) + `s`
return fmt.Sprintf(fcode, intStr)
}
//___________________________________________________________________________________
// Hack to just use json.Marshal for everything until
// we update for amino
//type JSONCodec struct{}
//func (jc JSONCodec) MarshalJSON(o interface{}) ([]byte, error) { return json.Marshal(o) }
//func (jc JSONCodec) UnmarshalJSON(bz []byte, o interface{}) error { return json.Unmarshal(bz, o) }
// Wraps r.MarshalText() in quotes to make it a valid JSON string.
//func (r Rat) MarshalAmino() (string, error) {
//bz, err := r.MarshalText()
//if err != nil {
//return "", err
//}
//return fmt.Sprintf(`%s`, bz), nil
//}
//// Requires a valid JSON string - strings quotes and calls UnmarshalText
//func (r *Rat) UnmarshalAmino(data string) (err error) {
////quote := []byte(`"`)
////if len(data) < 2 ||
////!bytes.HasPrefix(data, quote) ||
////!bytes.HasSuffix(data, quote) {
////return fmt.Errorf("JSON encoded Rat must be a quote-delimitted string")
////}
////data = bytes.Trim(data, `"`)
//return r.UnmarshalText([]byte(data))
//}