github.com/cosmos/cosmos-sdk@v0.50.10/x/group/internal/math/dec.go (about) 1 // Package math provides helper functions for doing mathematical calculations and parsing for the group module. 2 package math 3 4 import ( 5 "fmt" 6 7 "github.com/cockroachdb/apd/v2" 8 9 errorsmod "cosmossdk.io/errors" 10 11 "github.com/cosmos/cosmos-sdk/x/group/errors" 12 ) 13 14 // Dec is a wrapper struct around apd.Decimal that does no mutation of apd.Decimal's when performing 15 // arithmetic, instead creating a new apd.Decimal for every operation ensuring usage is safe. 16 // 17 // Using apd.Decimal directly can be unsafe because apd operations mutate the underlying Decimal, 18 // but when copying the big.Int structure can be shared between Decimal instances causing corruption. 19 // This was originally discovered in regen0-network/mainnet#15. 20 type Dec struct { 21 dec apd.Decimal 22 } 23 24 func NewPositiveDecFromString(s string) (Dec, error) { 25 d, err := NewDecFromString(s) 26 if err != nil { 27 return Dec{}, errors.ErrInvalidDecString.Wrap(err.Error()) 28 } 29 if !d.IsPositive() { 30 return Dec{}, errors.ErrInvalidDecString.Wrapf("expected a positive decimal, got %s", s) 31 } 32 return d, nil 33 } 34 35 func NewNonNegativeDecFromString(s string) (Dec, error) { 36 d, err := NewDecFromString(s) 37 if err != nil { 38 return Dec{}, errors.ErrInvalidDecString.Wrap(err.Error()) 39 } 40 if d.IsNegative() { 41 return Dec{}, errors.ErrInvalidDecString.Wrapf("expected a non-negative decimal, got %s", s) 42 } 43 return d, nil 44 } 45 46 func (x Dec) IsPositive() bool { 47 return !x.dec.Negative && !x.dec.IsZero() 48 } 49 50 // NewDecFromString returns a new Dec from a string 51 // It only support finite numbers, not NaN, +Inf, -Inf 52 func NewDecFromString(s string) (Dec, error) { 53 d, _, err := apd.NewFromString(s) 54 if err != nil { 55 return Dec{}, errors.ErrInvalidDecString.Wrap(err.Error()) 56 } 57 58 if d.Form != apd.Finite { 59 return Dec{}, errors.ErrInvalidDecString.Wrapf("expected a finite decimal, got %s", s) 60 } 61 62 return Dec{*d}, nil 63 } 64 65 func (x Dec) String() string { 66 return x.dec.Text('f') 67 } 68 69 func NewDecFromInt64(x int64) Dec { 70 var res Dec 71 res.dec.SetInt64(x) 72 return res 73 } 74 75 // Add returns a new Dec with value `x+y` without mutating any argument and error if 76 // there is an overflow. 77 func (x Dec) Add(y Dec) (Dec, error) { 78 var z Dec 79 _, err := apd.BaseContext.Add(&z.dec, &x.dec, &y.dec) 80 return z, errorsmod.Wrap(err, "decimal addition error") 81 } 82 83 // Sub returns a new Dec with value `x-y` without mutating any argument and error if 84 // there is an overflow. 85 func (x Dec) Sub(y Dec) (Dec, error) { 86 var z Dec 87 _, err := apd.BaseContext.Sub(&z.dec, &x.dec, &y.dec) 88 return z, errorsmod.Wrap(err, "decimal subtraction error") 89 } 90 91 func (x Dec) Int64() (int64, error) { 92 return x.dec.Int64() 93 } 94 95 func (x Dec) Cmp(y Dec) int { 96 return x.dec.Cmp(&y.dec) 97 } 98 99 func (x Dec) Equal(y Dec) bool { 100 return x.dec.Cmp(&y.dec) == 0 101 } 102 103 func (x Dec) IsNegative() bool { 104 return x.dec.Negative && !x.dec.IsZero() 105 } 106 107 // Add adds x and y 108 func Add(x, y Dec) (Dec, error) { 109 return x.Add(y) 110 } 111 112 var dec128Context = apd.Context{ 113 Precision: 34, 114 MaxExponent: apd.MaxExponent, 115 MinExponent: apd.MinExponent, 116 Traps: apd.DefaultTraps, 117 } 118 119 // Quo returns a new Dec with value `x/y` (formatted as decimal128, 34 digit precision) without mutating any 120 // argument and error if there is an overflow. 121 func (x Dec) Quo(y Dec) (Dec, error) { 122 var z Dec 123 _, err := dec128Context.Quo(&z.dec, &x.dec, &y.dec) 124 return z, errorsmod.Wrap(err, "decimal quotient error") 125 } 126 127 func (x Dec) IsZero() bool { 128 return x.dec.IsZero() 129 } 130 131 // SubNonNegative subtracts the value of y from x and returns the result with 132 // arbitrary precision. Returns an error if the result is negative. 133 func SubNonNegative(x, y Dec) (Dec, error) { 134 z, err := x.Sub(y) 135 if err != nil { 136 return Dec{}, err 137 } 138 139 if z.IsNegative() { 140 return z, fmt.Errorf("result negative during non-negative subtraction") 141 } 142 143 return z, nil 144 }