-
Notifications
You must be signed in to change notification settings - Fork 0
/
dec.go
136 lines (113 loc) · 3.53 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
// Package math provides helper functions for doing mathematical calculations and parsing for the ecocredit module.
package math
import (
"fmt"
"github.com/cockroachdb/apd/v2"
sdkerrors "github.com/mycodeku/transtionhelper/types/errors"
"github.com/mycodeku/transtionhelper/x/group/errors"
)
// Dec is a wrapper struct around apd.Decimal that does no mutation of apd.Decimal's when performing
// arithmetic, instead creating a new apd.Decimal for every operation ensuring usage is safe.
//
// Using apd.Decimal directly can be unsafe because apd operations mutate the underlying Decimal,
// but when copying the big.Int structure can be shared between Decimal instances causing corruption.
// This was originally discovered in regen0-network/mainnet#15.
type Dec struct {
dec apd.Decimal
}
func NewPositiveDecFromString(s string) (Dec, error) {
d, err := NewDecFromString(s)
if err != nil {
return Dec{}, errors.ErrInvalidDecString.Wrap(err.Error())
}
if !d.IsPositive() {
return Dec{}, errors.ErrInvalidDecString.Wrapf("expected a positive decimal, got %s", s)
}
return d, nil
}
func NewNonNegativeDecFromString(s string) (Dec, error) {
d, err := NewDecFromString(s)
if err != nil {
return Dec{}, errors.ErrInvalidDecString.Wrap(err.Error())
}
if d.IsNegative() {
return Dec{}, errors.ErrInvalidDecString.Wrapf("expected a non-negative decimal, got %s", s)
}
return d, nil
}
func (x Dec) IsPositive() bool {
return !x.dec.Negative && !x.dec.IsZero()
}
func NewDecFromString(s string) (Dec, error) {
d, _, err := apd.NewFromString(s)
if err != nil {
return Dec{}, errors.ErrInvalidDecString.Wrap(err.Error())
}
return Dec{*d}, nil
}
func (x Dec) String() string {
return x.dec.Text('f')
}
func NewDecFromInt64(x int64) Dec {
var res Dec
res.dec.SetInt64(x)
return res
}
// Add returns a new Dec with value `x+y` without mutating any argument and error if
// there is an overflow.
func (x Dec) Add(y Dec) (Dec, error) {
var z Dec
_, err := apd.BaseContext.Add(&z.dec, &x.dec, &y.dec)
return z, sdkerrors.Wrap(err, "decimal addition error")
}
// Sub returns a new Dec with value `x-y` without mutating any argument and error if
// there is an overflow.
func (x Dec) Sub(y Dec) (Dec, error) {
var z Dec
_, err := apd.BaseContext.Sub(&z.dec, &x.dec, &y.dec)
return z, sdkerrors.Wrap(err, "decimal subtraction error")
}
func (x Dec) Int64() (int64, error) {
return x.dec.Int64()
}
func (x Dec) Cmp(y Dec) int {
return x.dec.Cmp(&y.dec)
}
func (x Dec) IsEqual(y Dec) bool {
return x.dec.Cmp(&y.dec) == 0
}
func (x Dec) IsNegative() bool {
return x.dec.Negative && !x.dec.IsZero()
}
// Add adds x and y
func Add(x Dec, y Dec) (Dec, error) {
return x.Add(y)
}
var dec128Context = apd.Context{
Precision: 34,
MaxExponent: apd.MaxExponent,
MinExponent: apd.MinExponent,
Traps: apd.DefaultTraps,
}
// Quo returns a new Dec with value `x/y` (formatted as decimal128, 34 digit precision) without mutating any
// argument and error if there is an overflow.
func (x Dec) Quo(y Dec) (Dec, error) {
var z Dec
_, err := dec128Context.Quo(&z.dec, &x.dec, &y.dec)
return z, sdkerrors.Wrap(err, "decimal quotient error")
}
func (x Dec) IsZero() bool {
return x.dec.IsZero()
}
// SubNonNegative subtracts the value of y from x and returns the result with
// arbitrary precision. Returns an error if the result is negative.
func SubNonNegative(x Dec, y Dec) (Dec, error) {
z, err := x.Sub(y)
if err != nil {
return Dec{}, err
}
if z.IsNegative() {
return z, fmt.Errorf("result negative during non-negative subtraction")
}
return z, nil
}