-
Notifications
You must be signed in to change notification settings - Fork 201
/
dec.go
189 lines (167 loc) · 6.05 KB
/
dec.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
package common
import (
"fmt"
"math/big"
"strings"
sdk "github.com/cosmos/cosmos-sdk/types"
)
const (
// number of decimal places
PrecisionExponent = 18
// bits required to represent the above precision
// Ceiling[Log2[10^Precision - 1]]
DecimalPrecisionBits = 60
// decimalTruncateBits is the minimum number of bits removed
// by a truncate operation. It is equal to
// Floor[Log2[10^Precision - 1]].
decimalTruncateBits = DecimalPrecisionBits - 1
maxBitLen = 256
MaxDecBitLen = maxBitLen + decimalTruncateBits
)
var (
// precisionInt: 10 ** PrecisionExponent
precisionInt = new(big.Int).Exp(big.NewInt(10), big.NewInt(PrecisionExponent), nil)
// halfPrecisionInt: (10 ** PrecisionExponent) / 2
halfPrecisionInt = new(big.Int).Quo(precisionInt, big.NewInt(2))
oneInt = big.NewInt(1)
)
// MustSqrtDec computes the square root of the input decimal using its
// underlying big.Int. The big.Int.Sqrt method is part of the standard library,
// thoroughly tested, works at seemingly unbound precision (e.g. for numbers as
// large as 10**99.
// - NOTE, MustSqrtDec panics if it is called on a negative number, similar to the
// sdk.NewCoin and SqrtBigInt functions. A panic safe version of MustSqrtDec
// is available in the SqrtDec method.
func MustSqrtDec(dec sdk.Dec) sdk.Dec {
sqrtBigInt := MustSqrtBigInt(dec.BigInt())
precision := sdk.NewDecFromBigInt(PRECISION_MULT)
return sdk.NewDecFromBigInt(sqrtBigInt).Quo(precision)
}
// SqrtDec computes the square root of the input decimal using its
// underlying big.Int. SqrtDec is panic-safe and returns an error if the input
// decimal is negative.
//
// The big.Int.Sqrt method is part of the standard library,
// thoroughly tested, works at seemingly unbound precision (e.g. for numbers as
// large as 10**99.
func SqrtDec(dec sdk.Dec) (sdk.Dec, error) {
var sqrtDec sdk.Dec
var panicErr error = TryCatch(func() {
sqrtDec = MustSqrtDec(dec)
})()
return sqrtDec, panicErr
}
// MustSqrtBigInt returns the square root of the input.
// - NOTE: MustSqrtBigInt panics if it is called on a negative number because it uses
// the `big.Int.Sqrt` from the "math/big" package.
func MustSqrtBigInt(i *big.Int) *big.Int {
sqrtInt := &big.Int{}
return sqrtInt.Sqrt(i)
}
// SqrtInt is the panic-safe version of MustSqrtBigInt
func SqrtBigInt(i *big.Int) (*big.Int, error) {
sqrtInt := new(big.Int)
var panicErr error = TryCatch(func() {
*sqrtInt = *MustSqrtBigInt(i)
})()
return sqrtInt, panicErr
}
// BigIntPow10 returns a big int that is a power of 10, e.g. BigIngPow10(3)
// returns 1000. This function is useful for creating large numbers outside the
// range of an int64 or 18 decimal precision.
func BigIntPow10(power int64) *big.Int {
bigInt, _ := new(big.Int).SetString("1"+strings.Repeat("0", int(power)), 10)
return bigInt
}
// ————————————————————————————————————————————————
// Logic needed from private code in the Cosmos-SDK
// See https://github.com/cosmos/cosmos-sdk/blob/v0.45.12/types/decimal.go
//
const (
PRECISION = 18
)
var (
PRECISION_MULT = calcPrecisionMultiplier(0)
PRECISION_SQRT = int64(PRECISION / 2)
tenInt = big.NewInt(10)
)
// calcPrecisionMultiplier computes a multiplier needed to maintain a target
// precision defined by 10 ** (PRECISION_SQRT - prec).
// The maximum available precision is PRECISION_SQRT (9).
func calcPrecisionMultiplier(prec int64) *big.Int {
if prec > PRECISION_SQRT {
panic(fmt.Sprintf("too much precision, maximum %v, provided %v", PRECISION_SQRT, prec))
}
zerosToAdd := PRECISION_SQRT - prec
multiplier := new(big.Int).Exp(tenInt, big.NewInt(zerosToAdd), nil)
return multiplier
}
// ____
// __| |__ "chop 'em
// ` \ round!"
// ___|| ~ _ -bankers
// | | __
// | | | __|__|__
// |_____: / | $$$ |
// |________|
// ChopPrecisionAndRound: Remove a Precision amount of rightmost digits and
// perform bankers rounding on the remainder (gaussian rounding) on the digits
// which have been removed.
//
// Mutates the input. Use the non-mutative version if that is undesired
func ChopPrecisionAndRound(d *big.Int) *big.Int {
// remove the negative and add it back when returning
if d.Sign() == -1 {
// make d positive, compute chopped value, and then un-mutate d
d = d.Neg(d)
d = ChopPrecisionAndRound(d)
d = d.Neg(d)
return d
}
// Divide out the 'precisionInt', which truncates to a quotient and remainder.
quo, rem := d, big.NewInt(0)
quo, rem = quo.QuoRem(d, precisionInt, rem)
return BankersRound(quo, rem, halfPrecisionInt)
}
// BankersRound: Banker's rounding is a method commonly used in banking and
// accounting to reduce roudning bias when processing large volumes of rounded
// numbers.
//
// 1. If the remainder < half precision, round down
// 2. If the remainder > half precision, round up
// 3. If remainder == half precision, round to the nearest even number
//
// The name comes from the idea that it provides egalitarian rounding that
// doesn't consistently favor one party over another (e.g. always rounding up).
// With this method, rounding errors tend to cancel out rather than
// accumulating in one direction.
func BankersRound(quo, rem, halfPrecision *big.Int) *big.Int {
// Zero remainder after dividing precision means => no rounding is needed.
if rem.Sign() == 0 {
return quo
}
// Nonzero remainder after dividing precision means => do banker's rounding
switch rem.Cmp(halfPrecision) {
case -1:
return quo
case 1:
return quo.Add(quo, oneInt)
default:
// default case: bankers rounding must take place
// always round to an even number
if quo.Bit(0) == 0 {
return quo
}
return quo.Add(quo, oneInt)
}
}
// Clamp return the value if it is within the clampValue, otherwise return the clampValue.
// e.g. Clamp(1.5, 1) = 1, Clamp(-1.5, 1) = -1, Clamp(0.5, 1) = 0.5
func Clamp(value sdk.Dec, clampValue sdk.Dec) sdk.Dec {
if value.GT(clampValue) {
return clampValue
} else if value.LT(clampValue.Neg()) {
return clampValue.Neg()
}
return value
}