forked from cosmos/cosmos-sdk
/
rational.go
222 lines (190 loc) · 6.29 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
package types
import (
"fmt"
"math/big"
"strconv"
"strings"
"testing"
)
// "that's one big rat!"
// ______
// / / /\ \____oo
// __ /___...._____ _\o
// __| |_ |_
// NOTE: never use new(Rat) or else
// we will panic unmarshalling into the
// nil embedded big.Rat
type Rat struct {
big.Rat `json:"rat"`
}
// nolint - common values
func ZeroRat() Rat { return Rat{*big.NewRat(0, 1)} }
func OneRat() Rat { return 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")
}
}
// 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, ErrUnknownRequest("not a decimal string")
}
numStr = str[0]
case 2:
if len(str[0]) == 0 || len(str[1]) == 0 {
return f, ErrUnknownRequest("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, ErrUnknownRequest("not a decimal string")
}
num, errConv := strconv.Atoi(numStr)
if errConv != nil {
return f, ErrUnknownRequest(errConv.Error())
}
if neg {
num *= -1
}
return NewRat(int64(num), denom), nil
}
// NewRatFromBigInt constructs Rat from big.Int
func NewRatFromBigInt(num *big.Int, denom ...*big.Int) Rat {
switch len(denom) {
case 0:
return Rat{*new(big.Rat).SetInt(num)}
case 1:
return Rat{*new(big.Rat).SetFrac(num, denom[0])}
default:
panic("improper use of NewRatFromBigInt, can only have one denominator")
}
}
// NewRatFromInt constructs Rat from Int
func NewRatFromInt(num Int, denom ...Int) Rat {
switch len(denom) {
case 0:
return Rat{*new(big.Rat).SetInt(num.BigInt())}
case 1:
return Rat{*new(big.Rat).SetFrac(num.BigInt(), denom[0].BigInt())}
default:
panic("improper use of NewRatFromBigInt, can only have one denominator")
}
}
//nolint
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.Rat)) == 0 }
func (r Rat) GT(r2 Rat) bool { return (&(r.Rat)).Cmp(&(r2.Rat)) == 1 } // greater than
func (r Rat) LT(r2 Rat) bool { return (&(r.Rat)).Cmp(&(r2.Rat)) == -1 } // less than
func (r Rat) Mul(r2 Rat) Rat { return Rat{*new(big.Rat).Mul(&(r.Rat), &(r2.Rat))} } // Mul - multiplication
func (r Rat) Quo(r2 Rat) Rat { return Rat{*new(big.Rat).Quo(&(r.Rat), &(r2.Rat))} } // Quo - quotient
func (r Rat) Add(r2 Rat) Rat { return Rat{*new(big.Rat).Add(&(r.Rat), &(r2.Rat))} } // Add - addition
func (r Rat) Sub(r2 Rat) Rat { return Rat{*new(big.Rat).Sub(&(r.Rat), &(r2.Rat))} } // Sub - subtraction
func (r Rat) String() string { return fmt.Sprintf("%v/%v", r.Num(), r.Denom()) }
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.Rat.Num()
denom := r.Rat.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()
}
// EvaulateInt evaludates the rational using EvaluateBig
func (r Rat) EvaluateInt() Int {
return NewIntFromBigInt(r.EvaluateBig())
}
// round Rat with the provided precisionFactor
func (r Rat) Round(precisionFactor int64) Rat {
rTen := Rat{*new(big.Rat).Mul(&(r.Rat), big.NewRat(precisionFactor, 1))}
return Rat{*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)
}
//___________________________________________________________________________________
//Wraps r.MarshalText().
func (r Rat) MarshalAmino() (string, error) {
bz, err := (&(r.Rat)).MarshalText()
return string(bz), err
}
// Requires a valid JSON string - strings quotes and calls UnmarshalText
func (r *Rat) UnmarshalAmino(text string) (err error) {
tempRat := big.NewRat(0, 1)
err = tempRat.UnmarshalText([]byte(text))
if err != nil {
return err
}
r.Rat = *tempRat
return nil
}
//___________________________________________________________________________________
// helpers
// test if two rat arrays are the equal
func RatsEqual(r1s, r2s []Rat) bool {
if len(r1s) != len(r2s) {
return false
}
for i, r1 := range r1s {
if !r1.Equal(r2s[i]) {
return false
}
}
return true
}
// intended to be used with require/assert: require.True(RatEq(...))
func RatEq(t *testing.T, exp, got Rat) (*testing.T, bool, string, Rat, Rat) {
return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp, got
}