diff --git a/docs/build/building-modules/18-decimal-handling.md b/docs/build/building-modules/18-decimal-handling.md new file mode 100644 index 000000000000..a70d731bf1b5 --- /dev/null +++ b/docs/build/building-modules/18-decimal-handling.md @@ -0,0 +1,129 @@ +--- +sidebar_position: 1 +--- +# Decimal Handling in Cosmos SDK + +## Introduction + +In the Cosmos SDK, there are two types of decimals: `LegacyDec` and `Dec`. `LegacyDec` is the older decimal type that is still available for use, while `Dec` is the newer, more performant decimal type. The implementation of `Dec` is adapted from Regen Network's `regen-ledger`, specifically from [this module](https://github.com/regen-network/regen-ledger/tree/main/types/math). Migrating from `LegacyDec` to `Dec` involves state-breaking changes, specifically: + +* **Data Format**: The internal representation of decimals changes, affecting how data is stored and processed. +* **Precision Handling**: `Dec` supports flexible precision up to 34 decimal places, unlike `LegacyDec` which has a fixed precision of 18 decimal places. + +These changes require a state migration to update existing decimal values to the new format. It is recommended to use `Dec` for new modules to leverage its enhanced performance and flexibility. + +## Why the Change? + +* **Enhanced Precision**: `Dec` uses the [apd](https://github.com/cockroachdb/apd) library for arbitrary precision decimals, suitable for accurate financial calculations. +* **Immutable Operations**: `Dec` operations are safer for concurrent use, as they do not mutate the original values. +* **Better Performance**: `Dec` operations are faster and more efficient than `LegacyDec`.` + +## Using `Dec` in Modules that haven't used `LegacyDec` + +If you are creating a new module or updating an existing module that has not used `LegacyDec`, you can directly use `Dec` without any changes. + +As an example, we will use `DecCoin` which is a common type used in the Cosmos SDK. + + +```protobuf +message DecCoin { + option (gogoproto.equal) = true; + + string denom = 1; + string amount = 2 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.Dec", + (gogoproto.nullable) = false + ]; +} +``` + +How you can implement `Dec` in your module: + +```go +import ( + "cosmossdk.io/math" +) + +example := math.NewDecFromInt64(100) +``` + +## Modules migrating from `LegacyDec` to `Dec` + +When migrating from `LegacyDec` to `Dec`, you need to update your module to use the new decimal type. **These types are state breaking changes and require a migration.** + +## Precision Handling + +The reason for the state breaking change is the difference in precision handling between the two decimal types: + +* **LegacyDec**: Fixed precision of 18 decimal places. +* **Dec**: Flexible precision up to 34 decimal places using the apd library. + +## Byte Representation Changes Example + +The change in precision handling directly impacts the byte representation of decimal values: + +**Legacy Dec Byte Representation:** +`2333435363738393030303030303030303030303030303030303030` + +This example includes the value 123456789 followed by 18 zeros to maintain the fixed precision. + +**New Dec Byte Representation:** +`0a03617364121031323334353637383900000000000000` + +This example shows the value 123456789 without additional padding, reflecting the flexible precision handling of the new Dec type. + +## Impact of Precision Change + +The increase in precision from 18 to 34 decimal places allows for more detailed decimal values but requires data migration. This change in how data is formatted and stored is a key aspect of why the transition is considered state-breaking. + +## Example of State-Breaking Change + +The protobuf definitions for DecCoin illustrate the change in the custom type for the amount field. + +**Before:** + +```protobuf +message DecCoin { + option (gogoproto.equal) = true; + + string denom = 1; + string amount = 2 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; +} +``` + +**After:** + +```protobuf +message DecCoin { + option (gogoproto.equal) = true; + + string denom = 1; + string amount = 2 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.Dec", + (gogoproto.nullable) = false + ]; +} +``` + +## Converting `LegacyDec` to `Dec` without storing the data + +If you would like to convert a `LegacyDec` to a `Dec` without a state migration changing how the data is handled internally within the application logic and not how it's stored or represented. You can use the following methods. + +```go +func LegacyDecToDec(ld LegacyDec) (Dec, error) { + return NewDecFromString(ld.String()) +} +``` + +```go +func DecToLegacyDec(ld Dec) (LegacyDec, error) { + return LegacyDecFromString(ld.String()) +} +``` + diff --git a/go.mod b/go.mod index 7cfb65640f21..25715c486047 100644 --- a/go.mod +++ b/go.mod @@ -189,6 +189,7 @@ replace ( cosmossdk.io/depinject => ./depinject cosmossdk.io/log => ./log cosmossdk.io/store => ./store + cosmossdk.io/math => ./math cosmossdk.io/x/accounts => ./x/accounts cosmossdk.io/x/auth => ./x/auth cosmossdk.io/x/bank => ./x/bank diff --git a/math/dec.go b/math/dec.go index 0bcd40a4a4a6..14ca6fc8b2fe 100644 --- a/math/dec.go +++ b/math/dec.go @@ -1,971 +1,288 @@ package math import ( - "encoding/json" - "errors" - "fmt" "math/big" - "strconv" - "strings" - "testing" + + "cosmossdk.io/errors" + "github.com/cockroachdb/apd/v3" ) -// LegacyDec NOTE: never use new(Dec) or else we will panic unmarshalling into the -// nil embedded big.Int -type LegacyDec struct { - i *big.Int +// 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 } +// constants for more convenient intent behind dec.Cmp values. const ( - // LegacyPrecision number of decimal places - LegacyPrecision = 18 - - // LegacyDecimalPrecisionBits bits required to represent the above precision - // Ceiling[Log2[10^Precision - 1]] - LegacyDecimalPrecisionBits = 60 - - // decimalTruncateBits is the minimum number of bits removed - // by a truncate operation. It is equal to - // Floor[Log2[10^Precision - 1]]. - decimalTruncateBits = LegacyDecimalPrecisionBits - 1 - - maxDecBitLen = MaxBitLen + decimalTruncateBits - - // maxApproxRootIterations max number of iterations in ApproxRoot function - maxApproxRootIterations = 300 + GreaterThan = 1 + LessThan = -1 + EqualTo = 0 ) -var ( - precisionReuse = new(big.Int).Exp(big.NewInt(10), big.NewInt(LegacyPrecision), nil) - fivePrecision = new(big.Int).Quo(precisionReuse, big.NewInt(2)) - precisionMultipliers []*big.Int - zeroInt = big.NewInt(0) - oneInt = big.NewInt(1) - tenInt = big.NewInt(10) - smallestDec = LegacySmallestDec() -) +const mathCodespace = "math" -// Decimal errors var ( - ErrLegacyEmptyDecimalStr = errors.New("decimal string cannot be empty") - ErrLegacyInvalidDecimalLength = errors.New("invalid decimal length") - ErrLegacyInvalidDecimalStr = errors.New("invalid decimal string") + ErrInvalidDecString = errors.Register(mathCodespace, 1, "invalid decimal string") + ErrUnexpectedRounding = errors.Register(mathCodespace, 2, "unexpected rounding") + ErrNonIntegeral = errors.Register(mathCodespace, 3, "value is non-integral") + ErrInfiniteString = errors.Register(mathCodespace, 4, "value is infinite") ) -// Set precision multipliers -func init() { - precisionMultipliers = make([]*big.Int, LegacyPrecision+1) - for i := 0; i <= LegacyPrecision; i++ { - precisionMultipliers[i] = calcPrecisionMultiplier(int64(i)) - } -} - -func precisionInt() *big.Int { - return new(big.Int).Set(precisionReuse) -} - -func LegacyZeroDec() LegacyDec { return LegacyDec{new(big.Int).Set(zeroInt)} } -func LegacyOneDec() LegacyDec { return LegacyDec{precisionInt()} } -func LegacySmallestDec() LegacyDec { return LegacyDec{new(big.Int).Set(oneInt)} } - -// calculate the precision multiplier -func calcPrecisionMultiplier(prec int64) *big.Int { - if prec < 0 { - panic(fmt.Sprintf("negative precision %v", prec)) - } - - if prec > LegacyPrecision { - panic(fmt.Sprintf("too much precision, maximum %v, provided %v", LegacyPrecision, prec)) - } - zerosToAdd := LegacyPrecision - prec - multiplier := new(big.Int).Exp(tenInt, big.NewInt(zerosToAdd), nil) - return multiplier -} - -// get the precision multiplier, do not mutate result -func precisionMultiplier(prec int64) *big.Int { - if prec < 0 { - panic(fmt.Sprintf("negative precision %v", prec)) - } - - if prec > LegacyPrecision { - panic(fmt.Sprintf("too much precision, maximum %v, provided %v", LegacyPrecision, prec)) - } - return precisionMultipliers[prec] -} - -// LegacyNewDec create a new Dec from integer assuming whole number -func LegacyNewDec(i int64) LegacyDec { - return LegacyNewDecWithPrec(i, 0) -} - -// LegacyNewDecWithPrec create a new Dec from integer with decimal place at prec -// CONTRACT: prec <= Precision -func LegacyNewDecWithPrec(i, prec int64) LegacyDec { - bi := big.NewInt(i) - return LegacyDec{ - bi.Mul(bi, precisionMultiplier(prec)), - } -} - -// LegacyNewDecFromBigInt create a new Dec from big integer assuming whole numbers -// CONTRACT: prec <= Precision -func LegacyNewDecFromBigInt(i *big.Int) LegacyDec { - return LegacyNewDecFromBigIntWithPrec(i, 0) -} - -// LegacyNewDecFromBigIntWithPrec create a new Dec from big integer assuming whole numbers -// CONTRACT: prec <= Precision -func LegacyNewDecFromBigIntWithPrec(i *big.Int, prec int64) LegacyDec { - return LegacyDec{ - new(big.Int).Mul(i, precisionMultiplier(prec)), - } -} - -// LegacyNewDecFromInt create a new Dec from big integer assuming whole numbers -// CONTRACT: prec <= Precision -func LegacyNewDecFromInt(i Int) LegacyDec { - return LegacyNewDecFromIntWithPrec(i, 0) +// In cosmos-sdk#7773, decimal128 (with 34 digits of precision) was suggested for performing +// Quo/Mult arithmetic generically across the SDK. Even though the SDK +// has yet to support a GDA with decimal128 (34 digits), we choose to utilize it here. +// https://github.com/cosmos/cosmos-sdk/issues/7773#issuecomment-725006142 +var dec128Context = apd.Context{ + Precision: 34, + MaxExponent: apd.MaxExponent, + MinExponent: apd.MinExponent, + Traps: apd.DefaultTraps, } -// LegacyNewDecFromIntWithPrec create a new Dec from big integer with decimal place at prec -// CONTRACT: prec <= Precision -func LegacyNewDecFromIntWithPrec(i Int, prec int64) LegacyDec { - return LegacyDec{ - new(big.Int).Mul(i.BigIntMut(), precisionMultiplier(prec)), - } -} - -// LegacyNewDecFromStr create a decimal from an input decimal string. -// valid must come in the form: -// -// (-) whole integers (.) decimal integers -// -// examples of acceptable input include: -// -// -123.456 -// 456.7890 -// 345 -// -456789 -// -// NOTE - An error will return if more decimal places -// are provided in the string than the constant Precision. -// -// CONTRACT - This function does not mutate the input str. -func LegacyNewDecFromStr(str string) (LegacyDec, error) { - // first extract any negative symbol - neg := false - if len(str) > 0 && str[0] == '-' { - neg = true - str = str[1:] - } +type SetupConstraint func(Dec) error - if len(str) == 0 { - return LegacyDec{}, ErrLegacyEmptyDecimalStr - } - - strs := strings.Split(str, ".") - lenDecs := 0 - combinedStr := strs[0] - - if len(strs) == 2 { // has a decimal place - lenDecs = len(strs[1]) - if lenDecs == 0 || len(combinedStr) == 0 { - return LegacyDec{}, ErrLegacyInvalidDecimalLength +// AssertNotNegative greater or equal 0 +func AssertNotNegative() SetupConstraint { + return func(d Dec) error { + if d.IsNegative() { + return ErrInvalidDecString.Wrap("is negative") } - combinedStr += strs[1] - } else if len(strs) > 2 { - return LegacyDec{}, ErrLegacyInvalidDecimalStr - } - - if lenDecs > LegacyPrecision { - return LegacyDec{}, fmt.Errorf("value '%s' exceeds max precision by %d decimal places: max precision %d", str, LegacyPrecision-lenDecs, LegacyPrecision) - } - - // add some extra zero's to correct to the Precision factor - zerosToAdd := LegacyPrecision - lenDecs - zeros := strings.Repeat("0", zerosToAdd) - combinedStr += zeros - - combined, ok := new(big.Int).SetString(combinedStr, 10) // base 10 - if !ok { - return LegacyDec{}, fmt.Errorf("failed to set decimal string with base 10: %s", combinedStr) - } - if combined.BitLen() > maxDecBitLen { - return LegacyDec{}, fmt.Errorf("decimal '%s' out of range; bitLen: got %d, max %d", str, combined.BitLen(), maxDecBitLen) - } - if neg { - combined = new(big.Int).Neg(combined) - } - - return LegacyDec{combined}, nil -} - -// LegacyMustNewDecFromStr Decimal from string, panic on error -func LegacyMustNewDecFromStr(s string) LegacyDec { - dec, err := LegacyNewDecFromStr(s) - if err != nil { - panic(err) - } - return dec -} - -func (d LegacyDec) IsNil() bool { return d.i == nil } // is decimal nil -func (d LegacyDec) IsZero() bool { return (d.i).Sign() == 0 } // is equal to zero -func (d LegacyDec) IsNegative() bool { return (d.i).Sign() == -1 } // is negative -func (d LegacyDec) IsPositive() bool { return (d.i).Sign() == 1 } // is positive -func (d LegacyDec) Equal(d2 LegacyDec) bool { return (d.i).Cmp(d2.i) == 0 } // equal decimals -func (d LegacyDec) GT(d2 LegacyDec) bool { return (d.i).Cmp(d2.i) > 0 } // greater than -func (d LegacyDec) GTE(d2 LegacyDec) bool { return (d.i).Cmp(d2.i) >= 0 } // greater than or equal -func (d LegacyDec) LT(d2 LegacyDec) bool { return (d.i).Cmp(d2.i) < 0 } // less than -func (d LegacyDec) LTE(d2 LegacyDec) bool { return (d.i).Cmp(d2.i) <= 0 } // less than or equal -func (d LegacyDec) Neg() LegacyDec { return LegacyDec{new(big.Int).Neg(d.i)} } // reverse the decimal sign -func (d LegacyDec) NegMut() LegacyDec { d.i.Neg(d.i); return d } // reverse the decimal sign, mutable -func (d LegacyDec) Abs() LegacyDec { return LegacyDec{new(big.Int).Abs(d.i)} } // absolute value -func (d LegacyDec) AbsMut() LegacyDec { d.i.Abs(d.i); return d } // absolute value, mutable -func (d LegacyDec) Set(d2 LegacyDec) LegacyDec { d.i.Set(d2.i); return d } // set to existing dec value -func (d LegacyDec) Clone() LegacyDec { return LegacyDec{new(big.Int).Set(d.i)} } // clone new dec - -// BigInt returns a copy of the underlying big.Int. -func (d LegacyDec) BigInt() *big.Int { - if d.IsNil() { - return nil - } - - cp := new(big.Int) - return cp.Set(d.i) -} - -// BigIntMut converts LegacyDec to big.Int, mutative the input -func (d LegacyDec) BigIntMut() *big.Int { - if d.IsNil() { return nil } - - return d.i -} - -func (d LegacyDec) ImmutOp(op func(LegacyDec, LegacyDec) LegacyDec, d2 LegacyDec) LegacyDec { - return op(d.Clone(), d2) -} - -func (d LegacyDec) ImmutOpInt(op func(LegacyDec, Int) LegacyDec, d2 Int) LegacyDec { - return op(d.Clone(), d2) -} - -func (d LegacyDec) ImmutOpInt64(op func(LegacyDec, int64) LegacyDec, d2 int64) LegacyDec { - // TODO: use already allocated operand bigint to avoid - // newint each time, add mutex for race condition - // Issue: https://github.com/cosmos/cosmos-sdk/issues/11166 - return op(d.Clone(), d2) -} - -func (d LegacyDec) SetInt64(i int64) LegacyDec { - d.i.SetInt64(i) - d.i.Mul(d.i, precisionReuse) - return d -} - -// Add addition -func (d LegacyDec) Add(d2 LegacyDec) LegacyDec { - return d.ImmutOp(LegacyDec.AddMut, d2) -} - -// AddMut mutable addition -func (d LegacyDec) AddMut(d2 LegacyDec) LegacyDec { - d.i.Add(d.i, d2.i) - - if d.i.BitLen() > maxDecBitLen { - panic("Int overflow") - } - return d -} - -// Sub subtraction -func (d LegacyDec) Sub(d2 LegacyDec) LegacyDec { - return d.ImmutOp(LegacyDec.SubMut, d2) -} - -// SubMut mutable subtraction -func (d LegacyDec) SubMut(d2 LegacyDec) LegacyDec { - d.i.Sub(d.i, d2.i) - - if d.i.BitLen() > maxDecBitLen { - panic("Int overflow") - } - return d -} - -// Mul multiplication -func (d LegacyDec) Mul(d2 LegacyDec) LegacyDec { - return d.ImmutOp(LegacyDec.MulMut, d2) -} - -// MulMut mutable multiplication -func (d LegacyDec) MulMut(d2 LegacyDec) LegacyDec { - d.i.Mul(d.i, d2.i) - chopped := chopPrecisionAndRound(d.i) - - if chopped.BitLen() > maxDecBitLen { - panic("Int overflow") - } - *d.i = *chopped - return d -} - -// MulTruncate multiplication truncate -func (d LegacyDec) MulTruncate(d2 LegacyDec) LegacyDec { - return d.ImmutOp(LegacyDec.MulTruncateMut, d2) -} - -// MulTruncateMut mutable multiplication truncate -func (d LegacyDec) MulTruncateMut(d2 LegacyDec) LegacyDec { - d.i.Mul(d.i, d2.i) - chopPrecisionAndTruncate(d.i) - - if d.i.BitLen() > maxDecBitLen { - panic("Int overflow") - } - return d -} - -// MulRoundUp multiplication round up at precision end. -func (d LegacyDec) MulRoundUp(d2 LegacyDec) LegacyDec { - return d.ImmutOp(LegacyDec.MulRoundUpMut, d2) -} - -// MulRoundUpMut mutable multiplication with round up at precision end. -func (d LegacyDec) MulRoundUpMut(d2 LegacyDec) LegacyDec { - d.i.Mul(d.i, d2.i) - chopPrecisionAndRoundUp(d.i) - - if d.i.BitLen() > maxDecBitLen { - panic("Int overflow") - } - return d -} - -// MulInt multiplication -func (d LegacyDec) MulInt(i Int) LegacyDec { - return d.ImmutOpInt(LegacyDec.MulIntMut, i) -} - -func (d LegacyDec) MulIntMut(i Int) LegacyDec { - d.i.Mul(d.i, i.BigIntMut()) - if d.i.BitLen() > maxDecBitLen { - panic("Int overflow") - } - return d -} - -// MulInt64 multiplication with int64 -func (d LegacyDec) MulInt64(i int64) LegacyDec { - return d.ImmutOpInt64(LegacyDec.MulInt64Mut, i) -} - -func (d LegacyDec) MulInt64Mut(i int64) LegacyDec { - d.i.Mul(d.i, big.NewInt(i)) - - if d.i.BitLen() > maxDecBitLen { - panic("Int overflow") - } - return d -} - -// Quo quotient -func (d LegacyDec) Quo(d2 LegacyDec) LegacyDec { - return d.ImmutOp(LegacyDec.QuoMut, d2) -} - -var squaredPrecisionReuse = new(big.Int).Mul(precisionReuse, precisionReuse) - -// QuoMut mutable quotient -func (d LegacyDec) QuoMut(d2 LegacyDec) LegacyDec { - // multiply by precision twice - d.i.Mul(d.i, squaredPrecisionReuse) - d.i.Quo(d.i, d2.i) - - chopPrecisionAndRound(d.i) - if d.i.BitLen() > maxDecBitLen { - panic("Int overflow") - } - return d -} - -// QuoTruncate quotient truncate -func (d LegacyDec) QuoTruncate(d2 LegacyDec) LegacyDec { - return d.ImmutOp(LegacyDec.QuoTruncateMut, d2) -} - -// QuoTruncateMut divides the current LegacyDec value by the provided LegacyDec value, truncating the result. -func (d LegacyDec) QuoTruncateMut(d2 LegacyDec) LegacyDec { - // multiply precision once before performing division - d.i.Mul(d.i, precisionReuse) - d.i.Quo(d.i, d2.i) - - if d.i.BitLen() > maxDecBitLen { - panic("Int overflow") - } - return d -} - -// QuoRoundUp quotient, round up -func (d LegacyDec) QuoRoundUp(d2 LegacyDec) LegacyDec { - return d.ImmutOp(LegacyDec.QuoRoundupMut, d2) -} - -// QuoRoundupMut mutable quotient, round up -func (d LegacyDec) QuoRoundupMut(d2 LegacyDec) LegacyDec { - // multiply precision twice - d.i.Mul(d.i, precisionReuse) - _, rem := d.i.QuoRem(d.i, d2.i, big.NewInt(0)) - if rem.Sign() > 0 && d.IsNegative() == d2.IsNegative() || - rem.Sign() < 0 && d.IsNegative() != d2.IsNegative() { - d.i.Add(d.i, oneInt) - } - - if d.i.BitLen() > maxDecBitLen { - panic("Int overflow") - } - return d -} - -// QuoInt quotient -func (d LegacyDec) QuoInt(i Int) LegacyDec { - return d.ImmutOpInt(LegacyDec.QuoIntMut, i) -} - -func (d LegacyDec) QuoIntMut(i Int) LegacyDec { - d.i.Quo(d.i, i.BigIntMut()) - return d -} - -// QuoInt64 quotient with int64 -func (d LegacyDec) QuoInt64(i int64) LegacyDec { - return d.ImmutOpInt64(LegacyDec.QuoInt64Mut, i) -} - -func (d LegacyDec) QuoInt64Mut(i int64) LegacyDec { - d.i.Quo(d.i, big.NewInt(i)) - return d } -// ApproxRoot returns an approximate estimation of a Dec's positive real nth root -// using Newton's method (where n is positive). The algorithm starts with some guess and -// computes the sequence of improved guesses until an answer converges to an -// approximate answer. It returns `|d|.ApproxRoot() * -1` if input is negative. -// A maximum number of 100 iterations is used a backup boundary condition for -// cases where the answer never converges enough to satisfy the main condition. -func (d LegacyDec) ApproxRoot(root uint64) (guess LegacyDec, err error) { - defer func() { - if r := recover(); r != nil { - var ok bool - err, ok = r.(error) - if !ok { - err = errors.New("out of bounds") - } - } - }() - - if d.IsNegative() { - absRoot, err := d.Neg().ApproxRoot(root) - return absRoot.NegMut(), err - } - - // One decimal, that we invalidate later. Helps us save a heap allocation. - scratchOneDec := LegacyOneDec() - if root == 1 || d.IsZero() || d.Equal(scratchOneDec) { - return d, nil - } - - if root == 0 { - return scratchOneDec, nil - } - - guess, delta := scratchOneDec, LegacyOneDec() - - for iter := 0; iter < maxApproxRootIterations && delta.Abs().GT(smallestDec); iter++ { - prev := guess.Power(root - 1) - if prev.IsZero() { - prev = smallestDec +// AssertGreaterThanZero greater than 0 +func AssertGreaterThanZero() SetupConstraint { + return func(d Dec) error { + if !d.IsPositive() { + return ErrInvalidDecString.Wrap("is negative") } - delta.Set(d).QuoMut(prev) - delta.SubMut(guess) - delta.QuoInt64Mut(int64(root)) - - guess.AddMut(delta) + return nil } - - return guess, nil } -// Power returns a the result of raising to a positive integer power -func (d LegacyDec) Power(power uint64) LegacyDec { - res := LegacyDec{new(big.Int).Set(d.i)} - return res.PowerMut(power) -} - -func (d LegacyDec) PowerMut(power uint64) LegacyDec { - if power == 0 { - // Set to 1 with the correct precision. - d.i.Set(precisionReuse) - return d - } - tmp := LegacyOneDec() - - for i := power; i > 1; { - if i%2 != 0 { - tmp.MulMut(d) +// AssertMaxDecimals limit the decimal places +func AssertMaxDecimals(max uint32) SetupConstraint { + return func(d Dec) error { + if d.NumDecimalPlaces() > max { + return ErrInvalidDecString.Wrapf("exceeds maximum decimal places: %d", max) } - i /= 2 - d.MulMut(d) - } - - return d.MulMut(tmp) -} - -// ApproxSqrt is a wrapper around ApproxRoot for the common special case -// of finding the square root of a number. It returns -(sqrt(abs(d)) if input is negative. -func (d LegacyDec) ApproxSqrt() (LegacyDec, error) { - return d.ApproxRoot(2) -} - -// IsInteger is integer, e.g. decimals are zero -func (d LegacyDec) IsInteger() bool { - return new(big.Int).Rem(d.i, precisionReuse).Sign() == 0 -} - -// Format format decimal state -func (d LegacyDec) Format(s fmt.State, verb rune) { - _, err := s.Write([]byte(d.String())) - if err != nil { - panic(err) + return nil } } -func (d LegacyDec) String() string { - if d.i == nil { - return d.i.String() - } - - isNeg := d.IsNegative() - - if isNeg { - d = d.Neg() - } - - bzInt, err := d.i.MarshalText() +// NewDecFromString constructor +func NewDecFromString(s string, c ...SetupConstraint) (Dec, error) { + d, _, err := apd.NewFromString(s) if err != nil { - return "" - } - inputSize := len(bzInt) - - var bzStr []byte - - // TODO: Remove trailing zeros - // case 1, purely decimal - if inputSize <= LegacyPrecision { - bzStr = make([]byte, LegacyPrecision+2) - - // 0. prefix - bzStr[0] = byte('0') - bzStr[1] = byte('.') - - // set relevant digits to 0 - for i := 0; i < LegacyPrecision-inputSize; i++ { - bzStr[i+2] = byte('0') + return Dec{}, ErrInvalidDecString.Wrap(err.Error()) + } + + switch d.Form { + case apd.NaN, apd.NaNSignaling: + return Dec{}, ErrInvalidDecString.Wrap("not a number") + case apd.Infinite: + return Dec{}, ErrInfiniteString.Wrapf(s) + default: + result := Dec{*d} + for _, v := range c { + if err := v(result); err != nil { + return Dec{}, err + } } - - // set final digits - copy(bzStr[2+(LegacyPrecision-inputSize):], bzInt) - } else { - // inputSize + 1 to account for the decimal point that is being added - bzStr = make([]byte, inputSize+1) - decPointPlace := inputSize - LegacyPrecision - - copy(bzStr, bzInt[:decPointPlace]) // pre-decimal digits - bzStr[decPointPlace] = byte('.') // decimal point - copy(bzStr[decPointPlace+1:], bzInt[decPointPlace:]) // post-decimal digits - } - - if isNeg { - return "-" + string(bzStr) + return result, nil } - - return string(bzStr) } -// Float64 returns the float64 representation of a Dec. -// Will return the error if the conversion failed. -func (d LegacyDec) Float64() (float64, error) { - return strconv.ParseFloat(d.String(), 64) +func NewDecFromInt64(x int64) Dec { + var res Dec + res.dec.SetInt64(x) + return res } -// MustFloat64 returns the float64 representation of a Dec. -// Would panic if the conversion failed. -func (d LegacyDec) MustFloat64() float64 { - if value, err := strconv.ParseFloat(d.String(), 64); err != nil { - panic(err) - } else { - return value - } +// NewDecWithPrec returns a decimal with a value of coeff * 10^exp precision. +func NewDecWithPrec(coeff int64, exp int32) Dec { + var res Dec + res.dec.SetFinite(coeff, exp) + return res } -// ____ -// __| |__ "chop 'em -// ` \ round!" -// ___|| ~ _ -bankers -// | | __ -// | | | __|__|__ -// |_____: / | $$$ | -// |________| - -// 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 - } - - // get the truncated quotient and remainder - quo, rem := d, big.NewInt(0) - quo, rem = quo.QuoRem(d, precisionReuse, rem) - - if rem.Sign() == 0 { // remainder is zero - return quo - } - - switch rem.Cmp(fivePrecision) { - case -1: - return quo - case 1: - return quo.Add(quo, oneInt) - default: // bankers rounding must take place - // always round to an even number - if quo.Bit(0) == 0 { - return quo - } - return quo.Add(quo, oneInt) - } +// 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, errors.Wrap(err, "decimal addition error") } -func chopPrecisionAndRoundUp(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) - // truncate since d is negative... - chopPrecisionAndTruncate(d) - d = d.Neg(d) - return d - } - - // get the truncated quotient and remainder - quo, rem := d, big.NewInt(0) - quo, rem = quo.QuoRem(d, precisionReuse, rem) - - if rem.Sign() == 0 { // remainder is zero - return quo - } - - return quo.Add(quo, oneInt) +// 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, errors.Wrap(err, "decimal subtraction error") } -func chopPrecisionAndRoundNonMutative(d *big.Int) *big.Int { - tmp := new(big.Int).Set(d) - return chopPrecisionAndRound(tmp) +// 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, errors.Wrap(err, "decimal quotient error") } -// RoundInt64 rounds the decimal using bankers rounding -func (d LegacyDec) RoundInt64() int64 { - chopped := chopPrecisionAndRoundNonMutative(d.i) - if !chopped.IsInt64() { - panic("Int64() out of bound") +// MulExact returns a new dec with value x * y. The product must not round or +// ErrUnexpectedRounding will be returned. +func (x Dec) MulExact(y Dec) (Dec, error) { + var z Dec + condition, err := dec128Context.Mul(&z.dec, &x.dec, &y.dec) + if err != nil { + return z, err } - return chopped.Int64() -} - -// RoundInt round the decimal using bankers rounding -func (d LegacyDec) RoundInt() Int { - return NewIntFromBigIntMut(chopPrecisionAndRoundNonMutative(d.i)) -} - -// chopPrecisionAndTruncate is similar to chopPrecisionAndRound, -// but always rounds down. It does not mutate the input. -func chopPrecisionAndTruncate(d *big.Int) { - d.Quo(d, precisionReuse) -} - -func chopPrecisionAndTruncateNonMutative(d *big.Int) *big.Int { - tmp := new(big.Int).Set(d) - chopPrecisionAndTruncate(tmp) - return tmp -} - -// TruncateInt64 truncates the decimals from the number and returns an int64 -func (d LegacyDec) TruncateInt64() int64 { - chopped := chopPrecisionAndTruncateNonMutative(d.i) - if !chopped.IsInt64() { - panic("Int64() out of bound") + if condition.Rounded() { + return z, ErrUnexpectedRounding } - return chopped.Int64() -} - -// TruncateInt truncates the decimals from the number and returns an Int -func (d LegacyDec) TruncateInt() Int { - return NewIntFromBigIntMut(chopPrecisionAndTruncateNonMutative(d.i)) + return z, nil } -// TruncateDec truncates the decimals from the number and returns a Dec -func (d LegacyDec) TruncateDec() LegacyDec { - return LegacyNewDecFromBigInt(chopPrecisionAndTruncateNonMutative(d.i)) -} - -// Ceil returns the smallest integer value (as a decimal) that is greater than -// or equal to the given decimal. -func (d LegacyDec) Ceil() LegacyDec { - tmp := new(big.Int).Set(d.i) - - quo, rem := tmp, big.NewInt(0) - quo, rem = quo.QuoRem(tmp, precisionReuse, rem) - - // no need to round with a zero remainder regardless of sign - if rem.Sign() == 0 { - return LegacyNewDecFromBigInt(quo) - } else if rem.Sign() == -1 { - return LegacyNewDecFromBigInt(quo) +// QuoExact is a version of Quo that returns ErrUnexpectedRounding if any rounding occurred. +func (x Dec) QuoExact(y Dec) (Dec, error) { + var z Dec + condition, err := dec128Context.Quo(&z.dec, &x.dec, &y.dec) + if err != nil { + return z, err } - - if d.i.BitLen() >= maxDecBitLen { - panic("Int overflow") + if condition.Rounded() { + return z, ErrUnexpectedRounding } - - return LegacyNewDecFromBigInt(quo.Add(quo, oneInt)) -} - -// LegacyMaxSortableDec is the largest Dec that can be passed into SortableDecBytes() -// Its negative form is the least Dec that can be passed in. -var LegacyMaxSortableDec LegacyDec - -func init() { - LegacyMaxSortableDec = LegacyOneDec().Quo(LegacySmallestDec()) + return z, errors.Wrap(err, "decimal quotient error") } -// LegacyValidSortableDec ensures that a Dec is within the sortable bounds, -// a Dec can't have a precision of less than 10^-18. -// Max sortable decimal was set to the reciprocal of SmallestDec. -func LegacyValidSortableDec(dec LegacyDec) bool { - return dec.Abs().LTE(LegacyMaxSortableDec) +// QuoInteger returns a new integral Dec with value `x/y` (formatted as decimal128, with 34 digit precision) +// without mutating any argument and error if there is an overflow. +func (x Dec) QuoInteger(y Dec) (Dec, error) { + var z Dec + _, err := dec128Context.QuoInteger(&z.dec, &x.dec, &y.dec) + return z, errors.Wrap(err, "decimal quotient error") } -// LegacySortableDecBytes returns a byte slice representation of a Dec that can be sorted. -// Left and right pads with 0s so there are 18 digits to left and right of the decimal point. -// For this reason, there is a maximum and minimum value for this, enforced by ValidSortableDec. -func LegacySortableDecBytes(dec LegacyDec) []byte { - if !LegacyValidSortableDec(dec) { - panic("dec must be within bounds") - } - // Instead of adding an extra byte to all sortable decs in order to handle max sortable, we just - // makes its bytes be "max" which comes after all numbers in ASCIIbetical order - if dec.Equal(LegacyMaxSortableDec) { - return []byte("max") - } - // For the same reason, we make the bytes of minimum sortable dec be --, which comes before all numbers. - if dec.Equal(LegacyMaxSortableDec.Neg()) { - return []byte("--") - } - // We move the negative sign to the front of all the left padded 0s, to make negative numbers come before positive numbers - if dec.IsNegative() { - return append([]byte("-"), []byte(fmt.Sprintf(fmt.Sprintf("%%0%ds", LegacyPrecision*2+1), dec.Abs().String()))...) - } - return []byte(fmt.Sprintf(fmt.Sprintf("%%0%ds", LegacyPrecision*2+1), dec.String())) +// Rem returns the integral remainder from `x/y` (formatted as decimal128, with 34 digit precision) without +// mutating any argument and error if the integer part of x/y cannot fit in 34 digit precision +func (x Dec) Rem(y Dec) (Dec, error) { + var z Dec + _, err := dec128Context.Rem(&z.dec, &x.dec, &y.dec) + return z, errors.Wrap(err, "decimal remainder error") } -// reuse nil values -var nilJSON []byte - -func init() { - empty := new(big.Int) - bz, _ := empty.MarshalText() - nilJSON, _ = json.Marshal(string(bz)) +// Mul returns a new Dec with value `x*y` (formatted as decimal128, with 34 digit precision) without +// mutating any argument and error if there is an overflow. +func (x Dec) Mul(y Dec) (Dec, error) { + var z Dec + _, err := dec128Context.Mul(&z.dec, &x.dec, &y.dec) + return z, errors.Wrap(err, "decimal multiplication error") } -// MarshalJSON marshals the decimal -func (d LegacyDec) MarshalJSON() ([]byte, error) { - if d.i == nil { - return nilJSON, nil - } - return json.Marshal(d.String()) +// Int64 converts x to an int64 or returns an error if x cannot +// fit precisely into an int64. +func (x Dec) Int64() (int64, error) { + return x.dec.Int64() } -// UnmarshalJSON defines custom decoding scheme -func (d *LegacyDec) UnmarshalJSON(bz []byte) error { - if d.i == nil { - d.i = new(big.Int) - } - - var text string - err := json.Unmarshal(bz, &text) - if err != nil { - return err +// BigInt converts x to a *big.Int or returns an error if x cannot +// fit precisely into an *big.Int. +func (x Dec) BigInt() (*big.Int, error) { + y, _ := x.Reduce() + z := &big.Int{} + z, ok := z.SetString(y.String(), 10) + if !ok { + return nil, ErrNonIntegeral + } + return z, nil +} + +// SdkIntTrim rounds decimal number to the integer towards zero and converts it to `sdkmath.Int`. +// Panics if x is bigger the SDK Int max value +func (x Dec) SdkIntTrim() Int { + y, _ := x.Reduce() + r := y.dec.Coeff + if y.dec.Exponent != 0 { + decs := apd.NewBigInt(10) + if y.dec.Exponent > 0 { + decs.Exp(decs, apd.NewBigInt(int64(y.dec.Exponent)), nil) + r.Mul(&y.dec.Coeff, decs) + } else { + decs.Exp(decs, apd.NewBigInt(int64(-y.dec.Exponent)), nil) + r.Quo(&y.dec.Coeff, decs) + } } - - // TODO: Reuse dec allocation - newDec, err := LegacyNewDecFromStr(text) - if err != nil { - return err + if x.dec.Negative { + r.Neg(&r) } - - d.i = newDec.i - return nil + return NewIntFromBigInt(r.MathBigInt()) } -// MarshalYAML returns the YAML representation. -func (d LegacyDec) MarshalYAML() (interface{}, error) { - return d.String(), nil +func (x Dec) String() string { + return x.dec.Text('f') } -// Marshal implements the gogo proto custom type interface. -func (d LegacyDec) Marshal() ([]byte, error) { - i := d.i - if i == nil { - i = new(big.Int) - } - return i.MarshalText() +// Cmp compares x and y and returns: +// -1 if x < y +// 0 if x == y +// +1 if x > y +// undefined if d or x are NaN +func (x Dec) Cmp(y Dec) int { + return x.dec.Cmp(&y.dec) } -// MarshalTo implements the gogo proto custom type interface. -func (d *LegacyDec) MarshalTo(data []byte) (n int, err error) { - i := d.i - if i == nil { - i = new(big.Int) - } - - if i.Sign() == 0 { - copy(data, []byte{0x30}) - return 1, nil - } - - bz, err := d.Marshal() - if err != nil { - return 0, err - } - - copy(data, bz) - return len(bz), nil +func (x Dec) Equal(y Dec) bool { + return x.dec.Cmp(&y.dec) == 0 } -// Unmarshal implements the gogo proto custom type interface. -func (d *LegacyDec) Unmarshal(data []byte) error { - if len(data) == 0 { - d = nil - return nil - } - - if d.i == nil { - d.i = new(big.Int) - } - - if err := d.i.UnmarshalText(data); err != nil { - return err - } - - if d.i.BitLen() > maxDecBitLen { - return fmt.Errorf("decimal out of range; got: %d, max: %d", d.i.BitLen(), maxDecBitLen) - } - - return nil +// IsZero returns true if the decimal is zero. +func (x Dec) IsZero() bool { + return x.dec.IsZero() } -// Size implements the gogo proto custom type interface. -func (d *LegacyDec) Size() int { - bz, _ := d.Marshal() - return len(bz) +// IsNegative returns true if the decimal is negative. +func (x Dec) IsNegative() bool { + return x.dec.Negative && !x.dec.IsZero() } -// MarshalAmino Override Amino binary serialization by proxying to protobuf. -func (d LegacyDec) MarshalAmino() ([]byte, error) { return d.Marshal() } -func (d *LegacyDec) UnmarshalAmino(bz []byte) error { return d.Unmarshal(bz) } - -// helpers - -// test if two decimal arrays are equal -func LegacyDecsEqual(d1s, d2s []LegacyDec) bool { - if len(d1s) != len(d2s) { - return false - } - - for i, d1 := range d1s { - if !d1.Equal(d2s[i]) { - return false - } - } - return true +// IsPositive returns true if the decimal is positive. +func (x Dec) IsPositive() bool { + return !x.dec.Negative && !x.dec.IsZero() } -// LegacyMinDec minimum decimal between two -func LegacyMinDec(d1, d2 LegacyDec) LegacyDec { - if d1.LT(d2) { - return d1 - } - return d2 +// IsFinite returns true if the decimal is finite. +func (x Dec) IsFinite() bool { + return x.dec.Form == apd.Finite } -// LegacyMaxDec maximum decimal between two -func LegacyMaxDec(d1, d2 LegacyDec) LegacyDec { - if d1.LT(d2) { - return d2 +// NumDecimalPlaces returns the number of decimal places in x. +func (x Dec) NumDecimalPlaces() uint32 { + exp := x.dec.Exponent + if exp >= 0 { + return 0 } - return d1 + return uint32(-exp) } -// LegacyDecEq intended to be used with require/assert: require.True(DecEq(...)) -func LegacyDecEq(t *testing.T, exp, got LegacyDec) (*testing.T, bool, string, string, string) { - t.Helper() - return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp.String(), got.String() +// Reduce returns a copy of x with all trailing zeros removed +func (x Dec) Reduce() (Dec, int) { + y := Dec{} + _, n := y.dec.Reduce(&x.dec) + return y, n } -func LegacyDecApproxEq(t *testing.T, d1, d2, tol LegacyDec) (*testing.T, bool, string, string, string) { - t.Helper() - diff := d1.Sub(d2).Abs() - return t, diff.LTE(tol), "expected |d1 - d2| <:\t%v\ngot |d1 - d2| = \t\t%v", tol.String(), diff.String() +func (d Dec) Marshal() ([]byte, error) { + return d.dec.MarshalText() } -// FormatDec formats a decimal (as encoded in protobuf) into a value-rendered -// string following ADR-050. This function operates with string manipulation -// (instead of manipulating the sdk.Dec object). -func FormatDec(v string) (string, error) { - parts := strings.Split(v, ".") - if len(parts) > 2 { - return "", fmt.Errorf("invalid decimal: too many points in %s", v) - } - - intPart, err := FormatInt(parts[0]) - if err != nil { - return "", err - } - - if len(parts) == 1 { - return intPart, nil - } - - decPart := strings.TrimRight(parts[1], "0") - if len(decPart) == 0 { - return intPart, nil - } - - // Ensure that the decimal part has only digits. - // https://github.com/cosmos/cosmos-sdk/issues/12811 - if !hasOnlyDigits(decPart) { - return "", fmt.Errorf("non-digits detected after decimal point in: %q", decPart) - } - - return intPart + "." + decPart, nil +func (d *Dec) Unmarshal(data []byte) error { + return d.dec.UnmarshalText(data) } diff --git a/math/dec_bench_test.go b/math/dec_bench_test.go new file mode 100644 index 000000000000..b453a4d5f4bd --- /dev/null +++ b/math/dec_bench_test.go @@ -0,0 +1,348 @@ +package math + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func BenchmarkCompareLegacyDecAndNewDecQuotient(b *testing.B) { + specs := map[string]struct { + dividend, divisor string + }{ + "small/ small": { + dividend: "100", divisor: "5", + }, + "big18/ small": { + dividend: "999999999999999999", divisor: "10", + }, + "self18/ self18": { + dividend: "999999999999999999", divisor: "999999999999999999", + }, + "big18/ big18": { + dividend: "888888888888888888", divisor: "444444444444444444", + }, + "decimal18b/ decimal18c": { + dividend: "8.88888888888888888", divisor: "4.1234567890123", + }, + "small/ big18": { + dividend: "100", divisor: "999999999999999999", + }, + "big34/ big34": { + dividend: "9999999999999999999999999999999999", divisor: "1999999999999999999999999999999999", + }, + "negative big34": { + dividend: "-9999999999999999999999999999999999", divisor: "999999999999999999999999999", + }, + "decimal small": { + dividend: "0.0000000001", divisor: "10", + }, + "decimal small/decimal small ": { + dividend: "0.0000000001", divisor: "0.0001", + }, + } + for name, spec := range specs { + b.Run(name, func(b *testing.B) { + b.Run("LegacyDec", func(b *testing.B) { + dv, ds := LegacyMustNewDecFromStr(spec.dividend), LegacyMustNewDecFromStr(spec.divisor) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = dv.Quo(ds) + } + }) + + b.Run("NewDec", func(b *testing.B) { + dv, ds := must(NewDecFromString(spec.dividend)), must(NewDecFromString(spec.divisor)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = dv.Quo(ds) + } + }) + }) + } +} + +func BenchmarkCompareLegacyDecAndNewDecSum(b *testing.B) { + specs := map[string]struct { + summands []string + }{ + "1+2": { + summands: []string{"1", "2"}, + }, + "small numbers": { + summands: []string{"123", "0.2", "3.1415", "15"}, + }, + "medium numbers": { + summands: []string{"1234.567899", "9991345552.2340134"}, + }, + "big18": { + summands: []string{"123456789012345678", "123456789012345678", "123456789012345678", "123456789012345678", "123456789012345678", "123456789012345678"}, + }, + + "growing numbers": { + summands: []string{"1", "100", "1000", "100000", "10000000", "10000000000", "10000000000000", "100000000000000000"}, + }, + "decimals": { + summands: []string{"0.1", "0.01", "0.001", "0.000001", "0.00000001", "0.00000000001", "0.00000000000001", "0.000000000000000001"}, + }, + } + for name, spec := range specs { + b.Run(name, func(b *testing.B) { + b.Run("LegacyDec", func(b *testing.B) { + summands := make([]LegacyDec, len(spec.summands)) + for i, s := range spec.summands { + summands[i] = LegacyMustNewDecFromStr(s) + } + sum := LegacyNewDec(0) + b.ResetTimer() + for i := 0; i < b.N; i++ { + for _, s := range summands { + sum = sum.Add(s) + } + } + }) + + b.Run("NewDec", func(b *testing.B) { + summands := make([]Dec, len(spec.summands)) + for i, s := range spec.summands { + summands[i] = must(NewDecFromString(s)) + } + sum := NewDecFromInt64(0) + b.ResetTimer() + for i := 0; i < b.N; i++ { + for _, s := range summands { + sum, _ = sum.Add(s) + } + } + }) + }) + } +} + +func BenchmarkCompareLegacyDecAndNewDecSub(b *testing.B) { + specs := map[string]struct { + minuend string + subtrahends []string + }{ + "100 - 1 - 2": { + minuend: "100", + subtrahends: []string{"1", "2"}, + }, + "small numbers": { + minuend: "152.4013", + subtrahends: []string{"123", "0.2", "3.1415", "15"}, + }, + "10000000 - big18 numbers": { + minuend: "10000000", + subtrahends: []string{"123456789012345678", "123456789012345678", "123456789012345678", "123456789012345678", "123456789012345678", "123456789012345678"}, + }, + "10000000 - growing numbers": { + minuend: "10000000", + subtrahends: []string{"1", "100", "1000", "100000", "10000000", "10000000000", "10000000000000", "100000000000000000"}, + }, + "10000000 shrinking decimals": { + minuend: "10000000", + subtrahends: []string{"0.1", "0.01", "0.001", "0.000001", "0.00000001", "0.00000000001", "0.00000000000001", "0.000000000000000001"}, + }, + } + for name, spec := range specs { + b.Run(name, func(b *testing.B) { + b.Run("LegacyDec", func(b *testing.B) { + summands := make([]LegacyDec, len(spec.subtrahends)) + for i, s := range spec.subtrahends { + summands[i] = LegacyMustNewDecFromStr(s) + } + diff := LegacyMustNewDecFromStr(spec.minuend) + b.ResetTimer() + for i := 0; i < b.N; i++ { + for _, s := range summands { + diff = diff.Sub(s) + } + } + }) + + b.Run("NewDec", func(b *testing.B) { + summands := make([]Dec, len(spec.subtrahends)) + for i, s := range spec.subtrahends { + summands[i] = must(NewDecFromString(s)) + } + diff := must(NewDecFromString(spec.minuend)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + for _, s := range summands { + diff, _ = diff.Sub(s) + } + } + }) + }) + } +} + +func BenchmarkCompareLegacyDecAndNewDecMul(b *testing.B) { + specs := map[string]struct { + multiplier, multiplicant string + }{ + "small/ small": { + multiplier: "100", multiplicant: "5", + }, + "big18/ small": { + multiplier: "999999999999999999", multiplicant: "10", + }, + "self18/ self18": { + multiplier: "999999999999999999", multiplicant: "999999999999999999", + }, + "big18/ big18": { + multiplier: "888888888888888888", multiplicant: "444444444444444444", + }, + "decimal18b/ decimal18c": { + multiplier: "8.88888888888888888", multiplicant: "4.1234567890123", + }, + "small/ big18": { + multiplier: "100", multiplicant: "999999999999999999", + }, + "big34/ big34": { + multiplier: "9999999999999999999999999999999999", multiplicant: "1999999999999999999999999999999999", + }, + "negative big34": { + multiplier: "-9999999999999999999999999999999999", multiplicant: "999999999999999999999999999", + }, + "decimal small": { + multiplier: "0.0000000001", multiplicant: "10", + }, + "decimal small/decimal small ": { + multiplier: "0.0000000001", multiplicant: "0.0001", + }, + } + for name, spec := range specs { + b.Run(name, func(b *testing.B) { + b.Run("LegacyDec", func(b *testing.B) { + dv, ds := LegacyMustNewDecFromStr(spec.multiplier), LegacyMustNewDecFromStr(spec.multiplicant) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = dv.Mul(ds) + } + }) + + b.Run("NewDec", func(b *testing.B) { + dv, ds := must(NewDecFromString(spec.multiplier)), must(NewDecFromString(spec.multiplicant)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = dv.Mul(ds) + } + }) + }) + } +} + +func BenchmarkCompareLegacyDecAndNewDecMarshalUnmarshal(b *testing.B) { + specs := map[string]struct { + src string + }{ + "small": { + src: "1", + }, + "big18": { + src: "999999999999999999", + }, + "negative big34": { + src: "9999999999999999999999999999999999", + }, + "decimal": { + src: "12345.678901234341", + }, + } + for name, spec := range specs { + b.Run(name, func(b *testing.B) { + b.Run("LegacyDec", func(b *testing.B) { + src := LegacyMustNewDecFromStr(spec.src) + b.ResetTimer() + for i := 0; i < b.N; i++ { + bz, err := src.Marshal() + require.NoError(b, err) + var d LegacyDec + require.NoError(b, d.Unmarshal(bz)) + } + }) + + b.Run("NewDec", func(b *testing.B) { + src := must(NewDecFromString(spec.src)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + bz, err := src.Marshal() + require.NoError(b, err) + var d Dec + require.NoError(b, d.Unmarshal(bz)) + } + }) + }) + } +} + +func BenchmarkCompareLegacyDecAndNewDecQuoInteger(b *testing.B) { + legacyB1 := LegacyNewDec(100) + newB1 := NewDecFromInt64(100) + + b.Run("LegacyDec", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = legacyB1.Quo(LegacyNewDec(1)) + } + }) + + b.Run("NewDec", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = newB1.QuoInteger(NewDecFromInt64(1)) + } + }) +} + +func BenchmarkCompareLegacyAddAndDecAdd(b *testing.B) { + legacyB1 := LegacyNewDec(100) + legacyB2 := LegacyNewDec(5) + newB1 := NewDecFromInt64(100) + newB2 := NewDecFromInt64(5) + + b.Run("LegacyDec", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = legacyB1.Add(legacyB2) + } + }) + + b.Run("NewDec", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = newB1.Add(newB2) + } + }) +} + +func BenchmarkCompareLegacySubAndDecMul(b *testing.B) { + legacyB1 := LegacyNewDec(100) + legacyB2 := LegacyNewDec(5) + newB1 := NewDecFromInt64(100) + newB2 := NewDecFromInt64(5) + + b.Run("LegacyDec", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = legacyB1.Mul(legacyB2) + } + }) + + b.Run("NewDec", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = newB1.Mul(newB2) + } + }) +} + +func TestMigration(t *testing.T) { + legacyDec, _ := LegacyNewDecFromStr("123.456") + newDec, err := MigrateLegacyDecToDec(legacyDec) + require.NoError(t, err) + + expectedDec, _ := NewDecFromString("123.456") + require.True(t, newDec.Equal(expectedDec)) +} + +func MigrateLegacyDecToDec(legacyDec LegacyDec) (Dec, error) { + str := legacyDec.String() + return NewDecFromString(str) +} diff --git a/math/dec_legacy.go b/math/dec_legacy.go new file mode 100644 index 000000000000..d7cded6f2bb7 --- /dev/null +++ b/math/dec_legacy.go @@ -0,0 +1,975 @@ +package math + +import ( + "encoding/json" + "errors" + "fmt" + "math/big" + "strconv" + "strings" + "testing" +) + +// LegacyDec NOTE: never use new(Dec) or else we will panic unmarshalling into the +// nil embedded big.Int +type LegacyDec struct { + i *big.Int +} + +const ( + // LegacyPrecision number of decimal places + LegacyPrecision = 18 + + // LegacyDecimalPrecisionBits bits required to represent the above precision + // Ceiling[Log2[10^Precision - 1]] + LegacyDecimalPrecisionBits = 60 + + // decimalTruncateBits is the minimum number of bits removed + // by a truncate operation. It is equal to + // Floor[Log2[10^Precision - 1]]. + decimalTruncateBits = LegacyDecimalPrecisionBits - 1 + + maxDecBitLen = MaxBitLen + decimalTruncateBits + + // maxApproxRootIterations max number of iterations in ApproxRoot function + maxApproxRootIterations = 300 +) + +var ( + precisionReuse = new(big.Int).Exp(big.NewInt(10), big.NewInt(LegacyPrecision), nil) + fivePrecision = new(big.Int).Quo(precisionReuse, big.NewInt(2)) + precisionMultipliers []*big.Int + zeroInt = big.NewInt(0) + oneInt = big.NewInt(1) + tenInt = big.NewInt(10) + smallestDec = LegacySmallestDec() +) + +// Decimal errors +var ( + ErrLegacyEmptyDecimalStr = errors.New("decimal string cannot be empty") + ErrLegacyInvalidDecimalLength = errors.New("invalid decimal length") + ErrLegacyInvalidDecimalStr = errors.New("invalid decimal string") +) + +// Set precision multipliers +func init() { + precisionMultipliers = make([]*big.Int, LegacyPrecision+1) + for i := 0; i <= LegacyPrecision; i++ { + precisionMultipliers[i] = calcPrecisionMultiplier(int64(i)) + } +} + +func precisionInt() *big.Int { + return new(big.Int).Set(precisionReuse) +} + +func LegacyZeroDec() LegacyDec { return LegacyDec{new(big.Int).Set(zeroInt)} } +func LegacyOneDec() LegacyDec { return LegacyDec{precisionInt()} } +func LegacySmallestDec() LegacyDec { return LegacyDec{new(big.Int).Set(oneInt)} } + +// calculate the precision multiplier +func calcPrecisionMultiplier(prec int64) *big.Int { + if prec < 0 { + panic(fmt.Sprintf("negative precision %v", prec)) + } + + if prec > LegacyPrecision { + panic(fmt.Sprintf("too much precision, maximum %v, provided %v", LegacyPrecision, prec)) + } + zerosToAdd := LegacyPrecision - prec + multiplier := new(big.Int).Exp(tenInt, big.NewInt(zerosToAdd), nil) + return multiplier +} + +// get the precision multiplier, do not mutate result +func precisionMultiplier(prec int64) *big.Int { + if prec < 0 { + panic(fmt.Sprintf("negative precision %v", prec)) + } + + if prec > LegacyPrecision { + panic(fmt.Sprintf("too much precision, maximum %v, provided %v", LegacyPrecision, prec)) + } + return precisionMultipliers[prec] +} + +// LegacyNewDec create a new Dec from integer assuming whole number +func LegacyNewDec(i int64) LegacyDec { + return LegacyNewDecWithPrec(i, 0) +} + +// LegacyNewDecWithPrec create a new Dec from integer with decimal place at prec +// CONTRACT: prec <= Precision +func LegacyNewDecWithPrec(i, prec int64) LegacyDec { + bi := big.NewInt(i) + return LegacyDec{ + bi.Mul(bi, precisionMultiplier(prec)), + } +} + +// LegacyNewDecFromBigInt create a new Dec from big integer assuming whole numbers +// CONTRACT: prec <= Precision +func LegacyNewDecFromBigInt(i *big.Int) LegacyDec { + return LegacyNewDecFromBigIntWithPrec(i, 0) +} + +// LegacyNewDecFromBigIntWithPrec create a new Dec from big integer assuming whole numbers +// CONTRACT: prec <= Precision +func LegacyNewDecFromBigIntWithPrec(i *big.Int, prec int64) LegacyDec { + return LegacyDec{ + new(big.Int).Mul(i, precisionMultiplier(prec)), + } +} + +// LegacyNewDecFromInt create a new Dec from big integer assuming whole numbers +// CONTRACT: prec <= Precision +func LegacyNewDecFromInt(i Int) LegacyDec { + return LegacyNewDecFromIntWithPrec(i, 0) +} + +// LegacyNewDecFromIntWithPrec create a new Dec from big integer with decimal place at prec +// CONTRACT: prec <= Precision +func LegacyNewDecFromIntWithPrec(i Int, prec int64) LegacyDec { + return LegacyDec{ + new(big.Int).Mul(i.BigIntMut(), precisionMultiplier(prec)), + } +} + +// LegacyNewDecFromStr create a decimal from an input decimal string. +// valid must come in the form: +// +// (-) whole integers (.) decimal integers +// +// examples of acceptable input include: +// +// -123.456 +// 456.7890 +// 345 +// -456789 +// +// NOTE - An error will return if more decimal places +// are provided in the string than the constant Precision. +// +// CONTRACT - This function does not mutate the input str. +func LegacyNewDecFromStr(str string) (LegacyDec, error) { + // first extract any negative symbol + neg := false + if len(str) > 0 && str[0] == '-' { + neg = true + str = str[1:] + } + + if len(str) == 0 { + return LegacyDec{}, ErrLegacyEmptyDecimalStr + } + + strs := strings.Split(str, ".") + lenDecs := 0 + combinedStr := strs[0] + + if len(strs) == 2 { // has a decimal place + lenDecs = len(strs[1]) + if lenDecs == 0 || len(combinedStr) == 0 { + return LegacyDec{}, ErrLegacyInvalidDecimalLength + } + combinedStr += strs[1] + } else if len(strs) > 2 { + return LegacyDec{}, ErrLegacyInvalidDecimalStr + } + + if lenDecs > LegacyPrecision { + return LegacyDec{}, fmt.Errorf("value '%s' exceeds max precision by %d decimal places: max precision %d", str, LegacyPrecision-lenDecs, LegacyPrecision) + } + + // add some extra zero's to correct to the Precision factor + zerosToAdd := LegacyPrecision - lenDecs + zeros := strings.Repeat("0", zerosToAdd) + combinedStr += zeros + + combined, ok := new(big.Int).SetString(combinedStr, 10) // base 10 + if !ok { + return LegacyDec{}, fmt.Errorf("failed to set decimal string with base 10: %s", combinedStr) + } + if combined.BitLen() > maxDecBitLen { + return LegacyDec{}, fmt.Errorf("decimal '%s' out of range; bitLen: got %d, max %d", str, combined.BitLen(), maxDecBitLen) + } + if neg { + combined = new(big.Int).Neg(combined) + } + + return LegacyDec{combined}, nil +} + +// LegacyMustNewDecFromStr Decimal from string, panic on error +func LegacyMustNewDecFromStr(s string) LegacyDec { + dec, err := LegacyNewDecFromStr(s) + if err != nil { + panic(err) + } + return dec +} + +func (d LegacyDec) IsNil() bool { return d.i == nil } // is decimal nil +func (d LegacyDec) IsZero() bool { return (d.i).Sign() == 0 } // is equal to zero +func (d LegacyDec) IsNegative() bool { return (d.i).Sign() == -1 } // is negative +func (d LegacyDec) IsPositive() bool { return (d.i).Sign() == 1 } // is positive +func (d LegacyDec) Equal(d2 LegacyDec) bool { return (d.i).Cmp(d2.i) == 0 } // equal decimals +func (d LegacyDec) GT(d2 LegacyDec) bool { return (d.i).Cmp(d2.i) > 0 } // greater than +func (d LegacyDec) GTE(d2 LegacyDec) bool { return (d.i).Cmp(d2.i) >= 0 } // greater than or equal +func (d LegacyDec) LT(d2 LegacyDec) bool { return (d.i).Cmp(d2.i) < 0 } // less than +func (d LegacyDec) LTE(d2 LegacyDec) bool { return (d.i).Cmp(d2.i) <= 0 } // less than or equal +func (d LegacyDec) Neg() LegacyDec { return LegacyDec{new(big.Int).Neg(d.i)} } // reverse the decimal sign +func (d LegacyDec) NegMut() LegacyDec { d.i.Neg(d.i); return d } // reverse the decimal sign, mutable +func (d LegacyDec) Abs() LegacyDec { return LegacyDec{new(big.Int).Abs(d.i)} } // absolute value +func (d LegacyDec) AbsMut() LegacyDec { d.i.Abs(d.i); return d } // absolute value, mutable +func (d LegacyDec) Set(d2 LegacyDec) LegacyDec { d.i.Set(d2.i); return d } // set to existing dec value +func (d LegacyDec) Clone() LegacyDec { return LegacyDec{new(big.Int).Set(d.i)} } // clone new dec + +// BigInt returns a copy of the underlying big.Int. +func (d LegacyDec) BigInt() *big.Int { + if d.IsNil() { + return nil + } + + cp := new(big.Int) + return cp.Set(d.i) +} + +// BigIntMut converts LegacyDec to big.Int, mutative the input +func (d LegacyDec) BigIntMut() *big.Int { + if d.IsNil() { + return nil + } + + return d.i +} + +func (d LegacyDec) ImmutOp(op func(LegacyDec, LegacyDec) LegacyDec, d2 LegacyDec) LegacyDec { + return op(d.Clone(), d2) +} + +func (d LegacyDec) ImmutOpInt(op func(LegacyDec, Int) LegacyDec, d2 Int) LegacyDec { + return op(d.Clone(), d2) +} + +func (d LegacyDec) ImmutOpInt64(op func(LegacyDec, int64) LegacyDec, d2 int64) LegacyDec { + // TODO: use already allocated operand bigint to avoid + // newint each time, add mutex for race condition + // Issue: https://github.com/cosmos/cosmos-sdk/issues/11166 + return op(d.Clone(), d2) +} + +func (d LegacyDec) SetInt64(i int64) LegacyDec { + d.i.SetInt64(i) + d.i.Mul(d.i, precisionReuse) + return d +} + +// Add addition +func (d LegacyDec) Add(d2 LegacyDec) LegacyDec { + return d.ImmutOp(LegacyDec.AddMut, d2) +} + +// AddMut mutable addition +func (d LegacyDec) AddMut(d2 LegacyDec) LegacyDec { + d.i.Add(d.i, d2.i) + + if d.i.BitLen() > maxDecBitLen { + panic("Int overflow") + } + return d +} + +// Sub subtraction +func (d LegacyDec) Sub(d2 LegacyDec) LegacyDec { + return d.ImmutOp(LegacyDec.SubMut, d2) +} + +// SubMut mutable subtraction +func (d LegacyDec) SubMut(d2 LegacyDec) LegacyDec { + d.i.Sub(d.i, d2.i) + + if d.i.BitLen() > maxDecBitLen { + panic("Int overflow") + } + return d +} + +// Mul multiplication +func (d LegacyDec) Mul(d2 LegacyDec) LegacyDec { + return d.ImmutOp(LegacyDec.MulMut, d2) +} + +// MulMut mutable multiplication +func (d LegacyDec) MulMut(d2 LegacyDec) LegacyDec { + d.i.Mul(d.i, d2.i) + chopped := chopPrecisionAndRound(d.i) + + if chopped.BitLen() > maxDecBitLen { + panic("Int overflow") + } + *d.i = *chopped + return d +} + +// MulTruncate multiplication truncate +func (d LegacyDec) MulTruncate(d2 LegacyDec) LegacyDec { + return d.ImmutOp(LegacyDec.MulTruncateMut, d2) +} + +// MulTruncateMut mutable multiplication truncate +func (d LegacyDec) MulTruncateMut(d2 LegacyDec) LegacyDec { + d.i.Mul(d.i, d2.i) + chopPrecisionAndTruncate(d.i) + + if d.i.BitLen() > maxDecBitLen { + panic("Int overflow") + } + return d +} + +// MulRoundUp multiplication round up at precision end. +func (d LegacyDec) MulRoundUp(d2 LegacyDec) LegacyDec { + return d.ImmutOp(LegacyDec.MulRoundUpMut, d2) +} + +// MulRoundUpMut mutable multiplication with round up at precision end. +func (d LegacyDec) MulRoundUpMut(d2 LegacyDec) LegacyDec { + d.i.Mul(d.i, d2.i) + chopPrecisionAndRoundUp(d.i) + + if d.i.BitLen() > maxDecBitLen { + panic("Int overflow") + } + return d +} + +// MulInt multiplication +func (d LegacyDec) MulInt(i Int) LegacyDec { + return d.ImmutOpInt(LegacyDec.MulIntMut, i) +} + +func (d LegacyDec) MulIntMut(i Int) LegacyDec { + d.i.Mul(d.i, i.BigIntMut()) + if d.i.BitLen() > maxDecBitLen { + panic("Int overflow") + } + return d +} + +// MulInt64 multiplication with int64 +func (d LegacyDec) MulInt64(i int64) LegacyDec { + return d.ImmutOpInt64(LegacyDec.MulInt64Mut, i) +} + +func (d LegacyDec) MulInt64Mut(i int64) LegacyDec { + d.i.Mul(d.i, big.NewInt(i)) + + if d.i.BitLen() > maxDecBitLen { + panic("Int overflow") + } + return d +} + +// Quo quotient +func (d LegacyDec) Quo(d2 LegacyDec) LegacyDec { + return d.ImmutOp(LegacyDec.QuoMut, d2) +} + +var squaredPrecisionReuse = new(big.Int).Mul(precisionReuse, precisionReuse) + +// QuoMut mutable quotient +func (d LegacyDec) QuoMut(d2 LegacyDec) LegacyDec { + // multiply by precision twice + d.i.Mul(d.i, squaredPrecisionReuse) + d.i.Quo(d.i, d2.i) + + chopPrecisionAndRound(d.i) + if d.i.BitLen() > maxDecBitLen { + panic("Int overflow") + } + return d +} + +// QuoTruncate quotient truncate +func (d LegacyDec) QuoTruncate(d2 LegacyDec) LegacyDec { + return d.ImmutOp(LegacyDec.QuoTruncateMut, d2) +} + +// QuoTruncateMut divides the current LegacyDec value by the provided LegacyDec value, truncating the result. +func (d LegacyDec) QuoTruncateMut(d2 LegacyDec) LegacyDec { + // multiply precision once before performing division + d.i.Mul(d.i, precisionReuse) + d.i.Quo(d.i, d2.i) + + if d.i.BitLen() > maxDecBitLen { + panic("Int overflow") + } + return d +} + +// QuoRoundUp quotient, round up +func (d LegacyDec) QuoRoundUp(d2 LegacyDec) LegacyDec { + return d.ImmutOp(LegacyDec.QuoRoundupMut, d2) +} + +// QuoRoundupMut mutable quotient, round up +func (d LegacyDec) QuoRoundupMut(d2 LegacyDec) LegacyDec { + // multiply precision twice + d.i.Mul(d.i, precisionReuse) + _, rem := d.i.QuoRem(d.i, d2.i, big.NewInt(0)) + if rem.Sign() > 0 && d.IsNegative() == d2.IsNegative() || + rem.Sign() < 0 && d.IsNegative() != d2.IsNegative() { + d.i.Add(d.i, oneInt) + } + + if d.i.BitLen() > maxDecBitLen { + panic("Int overflow") + } + return d +} + +// QuoInt quotient +func (d LegacyDec) QuoInt(i Int) LegacyDec { + return d.ImmutOpInt(LegacyDec.QuoIntMut, i) +} + +func (d LegacyDec) QuoIntMut(i Int) LegacyDec { + d.i.Quo(d.i, i.BigIntMut()) + return d +} + +// QuoInt64 quotient with int64 +func (d LegacyDec) QuoInt64(i int64) LegacyDec { + return d.ImmutOpInt64(LegacyDec.QuoInt64Mut, i) +} + +func (d LegacyDec) QuoInt64Mut(i int64) LegacyDec { + d.i.Quo(d.i, big.NewInt(i)) + return d +} + +// ApproxRoot returns an approximate estimation of a Dec's positive real nth root +// using Newton's method (where n is positive). The algorithm starts with some guess and +// computes the sequence of improved guesses until an answer converges to an +// approximate answer. It returns `|d|.ApproxRoot() * -1` if input is negative. +// A maximum number of 100 iterations is used a backup boundary condition for +// cases where the answer never converges enough to satisfy the main condition. +func (d LegacyDec) ApproxRoot(root uint64) (guess LegacyDec, err error) { + defer func() { + if r := recover(); r != nil { + var ok bool + err, ok = r.(error) + if !ok { + err = errors.New("out of bounds") + } + } + }() + + if d.IsNegative() { + absRoot, err := d.Neg().ApproxRoot(root) + return absRoot.NegMut(), err + } + + // One decimal, that we invalidate later. Helps us save a heap allocation. + scratchOneDec := LegacyOneDec() + if root == 1 || d.IsZero() || d.Equal(scratchOneDec) { + return d, nil + } + + if root == 0 { + return scratchOneDec, nil + } + + guess, delta := scratchOneDec, LegacyOneDec() + + for iter := 0; iter < maxApproxRootIterations && delta.Abs().GT(smallestDec); iter++ { + prev := guess.Power(root - 1) + if prev.IsZero() { + prev = smallestDec + } + delta.Set(d).QuoMut(prev) + delta.SubMut(guess) + delta.QuoInt64Mut(int64(root)) + + guess.AddMut(delta) + } + + return guess, nil +} + +// Power returns a the result of raising to a positive integer power +func (d LegacyDec) Power(power uint64) LegacyDec { + res := LegacyDec{new(big.Int).Set(d.i)} + return res.PowerMut(power) +} + +func (d LegacyDec) PowerMut(power uint64) LegacyDec { + if power == 0 { + // Set to 1 with the correct precision. + d.i.Set(precisionReuse) + return d + } + tmp := LegacyOneDec() + + for i := power; i > 1; { + if i%2 != 0 { + tmp.MulMut(d) + } + i /= 2 + d.MulMut(d) + } + + return d.MulMut(tmp) +} + +// ApproxSqrt is a wrapper around ApproxRoot for the common special case +// of finding the square root of a number. It returns -(sqrt(abs(d)) if input is negative. +func (d LegacyDec) ApproxSqrt() (LegacyDec, error) { + return d.ApproxRoot(2) +} + +// IsInteger is integer, e.g. decimals are zero +func (d LegacyDec) IsInteger() bool { + return new(big.Int).Rem(d.i, precisionReuse).Sign() == 0 +} + +// Format format decimal state +func (d LegacyDec) Format(s fmt.State, verb rune) { + _, err := s.Write([]byte(d.String())) + if err != nil { + panic(err) + } +} + +func (d LegacyDec) String() string { + if d.i == nil { + return d.i.String() + } + + isNeg := d.IsNegative() + + if isNeg { + d = d.Neg() + } + + bzInt, err := d.i.MarshalText() + if err != nil { + return "" + } + inputSize := len(bzInt) + + var bzStr []byte + + // TODO: Remove trailing zeros + // case 1, purely decimal + if inputSize <= LegacyPrecision { + bzStr = make([]byte, LegacyPrecision+2) + + // 0. prefix + bzStr[0] = byte('0') + bzStr[1] = byte('.') + + // set relevant digits to 0 + for i := 0; i < LegacyPrecision-inputSize; i++ { + bzStr[i+2] = byte('0') + } + + // set final digits + copy(bzStr[2+(LegacyPrecision-inputSize):], bzInt) + } else { + // inputSize + 1 to account for the decimal point that is being added + bzStr = make([]byte, inputSize+1) + decPointPlace := inputSize - LegacyPrecision + + copy(bzStr, bzInt[:decPointPlace]) // pre-decimal digits + bzStr[decPointPlace] = byte('.') // decimal point + copy(bzStr[decPointPlace+1:], bzInt[decPointPlace:]) // post-decimal digits + } + + if isNeg { + return "-" + string(bzStr) + } + + return string(bzStr) +} + +// Float64 returns the float64 representation of a Dec. +// Will return the error if the conversion failed. +func (d LegacyDec) Float64() (float64, error) { + return strconv.ParseFloat(d.String(), 64) +} + +// MustFloat64 returns the float64 representation of a Dec. +// Would panic if the conversion failed. +func (d LegacyDec) MustFloat64() float64 { + if value, err := strconv.ParseFloat(d.String(), 64); err != nil { + panic(err) + } else { + return value + } +} + +// ____ +// __| |__ "chop 'em +// ` \ round!" +// ___|| ~ _ -bankers +// | | __ +// | | | __|__|__ +// |_____: / | $$$ | +// |________| + +// 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 + } + + // get the truncated quotient and remainder + quo, rem := d, big.NewInt(0) + quo, rem = quo.QuoRem(d, precisionReuse, rem) + + if rem.Sign() == 0 { // remainder is zero + return quo + } + + switch rem.Cmp(fivePrecision) { + case -1: + return quo + case 1: + return quo.Add(quo, oneInt) + default: // bankers rounding must take place + // always round to an even number + if quo.Bit(0) == 0 { + return quo + } + return quo.Add(quo, oneInt) + } +} + +func chopPrecisionAndRoundUp(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) + // truncate since d is negative... + chopPrecisionAndTruncate(d) + d = d.Neg(d) + return d + } + + // get the truncated quotient and remainder + quo, rem := d, big.NewInt(0) + quo, rem = quo.QuoRem(d, precisionReuse, rem) + + if rem.Sign() == 0 { // remainder is zero + return quo + } + + return quo.Add(quo, oneInt) +} + +func chopPrecisionAndRoundNonMutative(d *big.Int) *big.Int { + tmp := new(big.Int).Set(d) + return chopPrecisionAndRound(tmp) +} + +// RoundInt64 rounds the decimal using bankers rounding +func (d LegacyDec) RoundInt64() int64 { + chopped := chopPrecisionAndRoundNonMutative(d.i) + if !chopped.IsInt64() { + panic("Int64() out of bound") + } + return chopped.Int64() +} + +// RoundInt round the decimal using bankers rounding +func (d LegacyDec) RoundInt() Int { + return NewIntFromBigIntMut(chopPrecisionAndRoundNonMutative(d.i)) +} + +// chopPrecisionAndTruncate is similar to chopPrecisionAndRound, +// but always rounds down. It does not mutate the input. +func chopPrecisionAndTruncate(d *big.Int) { + d.Quo(d, precisionReuse) +} + +func chopPrecisionAndTruncateNonMutative(d *big.Int) *big.Int { + tmp := new(big.Int).Set(d) + chopPrecisionAndTruncate(tmp) + return tmp +} + +// TruncateInt64 truncates the decimals from the number and returns an int64 +func (d LegacyDec) TruncateInt64() int64 { + chopped := chopPrecisionAndTruncateNonMutative(d.i) + if !chopped.IsInt64() { + panic("Int64() out of bound") + } + return chopped.Int64() +} + +// TruncateInt truncates the decimals from the number and returns an Int +func (d LegacyDec) TruncateInt() Int { + return NewIntFromBigIntMut(chopPrecisionAndTruncateNonMutative(d.i)) +} + +// TruncateDec truncates the decimals from the number and returns a Dec +func (d LegacyDec) TruncateDec() LegacyDec { + return LegacyNewDecFromBigInt(chopPrecisionAndTruncateNonMutative(d.i)) +} + +// Ceil returns the smallest integer value (as a decimal) that is greater than +// or equal to the given decimal. +func (d LegacyDec) Ceil() LegacyDec { + tmp := new(big.Int).Set(d.i) + + quo, rem := tmp, big.NewInt(0) + quo, rem = quo.QuoRem(tmp, precisionReuse, rem) + + // no need to round with a zero remainder regardless of sign + if rem.Sign() == 0 { + return LegacyNewDecFromBigInt(quo) + } else if rem.Sign() == -1 { + return LegacyNewDecFromBigInt(quo) + } + + if d.i.BitLen() >= maxDecBitLen { + panic("Int overflow") + } + + return LegacyNewDecFromBigInt(quo.Add(quo, oneInt)) +} + +// LegacyMaxSortableDec is the largest Dec that can be passed into SortableDecBytes() +// Its negative form is the least Dec that can be passed in. +var LegacyMaxSortableDec LegacyDec + +func init() { + LegacyMaxSortableDec = LegacyOneDec().Quo(LegacySmallestDec()) +} + +// LegacyValidSortableDec ensures that a Dec is within the sortable bounds, +// a Dec can't have a precision of less than 10^-18. +// Max sortable decimal was set to the reciprocal of SmallestDec. +func LegacyValidSortableDec(dec LegacyDec) bool { + return dec.Abs().LTE(LegacyMaxSortableDec) +} + +// LegacySortableDecBytes returns a byte slice representation of a Dec that can be sorted. +// Left and right pads with 0s so there are 18 digits to left and right of the decimal point. +// For this reason, there is a maximum and minimum value for this, enforced by ValidSortableDec. +func LegacySortableDecBytes(dec LegacyDec) []byte { + if !LegacyValidSortableDec(dec) { + panic("dec must be within bounds") + } + // Instead of adding an extra byte to all sortable decs in order to handle max sortable, we just + // makes its bytes be "max" which comes after all numbers in ASCIIbetical order + if dec.Equal(LegacyMaxSortableDec) { + return []byte("max") + } + // For the same reason, we make the bytes of minimum sortable dec be --, which comes before all numbers. + if dec.Equal(LegacyMaxSortableDec.Neg()) { + return []byte("--") + } + // We move the negative sign to the front of all the left padded 0s, to make negative numbers come before positive numbers + if dec.IsNegative() { + return append([]byte("-"), []byte(fmt.Sprintf(fmt.Sprintf("%%0%ds", LegacyPrecision*2+1), dec.Abs().String()))...) + } + return []byte(fmt.Sprintf(fmt.Sprintf("%%0%ds", LegacyPrecision*2+1), dec.String())) +} + +// reuse nil values +var nilJSON []byte + +func init() { + empty := new(big.Int) + bz, _ := empty.MarshalText() + nilJSON, _ = json.Marshal(string(bz)) +} + +// MarshalJSON marshals the decimal +func (d LegacyDec) MarshalJSON() ([]byte, error) { + if d.i == nil { + return nilJSON, nil + } + return json.Marshal(d.String()) +} + +// UnmarshalJSON defines custom decoding scheme +func (d *LegacyDec) UnmarshalJSON(bz []byte) error { + if d.i == nil { + d.i = new(big.Int) + } + + var text string + err := json.Unmarshal(bz, &text) + if err != nil { + return err + } + + // TODO: Reuse dec allocation + newDec, err := LegacyNewDecFromStr(text) + if err != nil { + return err + } + + d.i = newDec.i + return nil +} + +// MarshalYAML returns the YAML representation. +func (d LegacyDec) MarshalYAML() (interface{}, error) { + return d.String(), nil +} + +// Marshal implements the gogo proto custom type interface. +func (d LegacyDec) Marshal() ([]byte, error) { + i := d.i + if i == nil { + i = new(big.Int) + } + return i.MarshalText() +} + +// MarshalTo implements the gogo proto custom type interface. +func (d *LegacyDec) MarshalTo(data []byte) (n int, err error) { + i := d.i + if i == nil { + i = new(big.Int) + } + + if i.Sign() == 0 { + copy(data, []byte{0x30}) + return 1, nil + } + + bz, err := d.Marshal() + if err != nil { + return 0, err + } + + copy(data, bz) + return len(bz), nil +} + +// Unmarshal implements the gogo proto custom type interface. +func (d *LegacyDec) Unmarshal(data []byte) error { + if len(data) == 0 { + d = nil + return nil + } + + if d.i == nil { + d.i = new(big.Int) + } + + if err := d.i.UnmarshalText(data); err != nil { + return err + } + + if d.i.BitLen() > maxDecBitLen { + return fmt.Errorf("decimal out of range; got: %d, max: %d", d.i.BitLen(), maxDecBitLen) + } + + return nil +} + +// Size implements the gogo proto custom type interface. +func (d *LegacyDec) Size() int { + bz, _ := d.Marshal() + return len(bz) +} + +// MarshalAmino Override Amino binary serialization by proxying to protobuf. +func (d LegacyDec) MarshalAmino() ([]byte, error) { return d.Marshal() } +func (d *LegacyDec) UnmarshalAmino(bz []byte) error { return d.Unmarshal(bz) } + +// helpers + +// test if two decimal arrays are equal +func LegacyDecsEqual(d1s, d2s []LegacyDec) bool { + if len(d1s) != len(d2s) { + return false + } + + for i, d1 := range d1s { + if !d1.Equal(d2s[i]) { + return false + } + } + return true +} + +// LegacyMinDec minimum decimal between two +func LegacyMinDec(d1, d2 LegacyDec) LegacyDec { + if d1.LT(d2) { + return d1 + } + return d2 +} + +// LegacyMaxDec maximum decimal between two +func LegacyMaxDec(d1, d2 LegacyDec) LegacyDec { + if d1.LT(d2) { + return d2 + } + return d1 +} + +// LegacyDecEq intended to be used with require/assert: require.True(DecEq(...)) +func LegacyDecEq(t *testing.T, exp, got LegacyDec) (*testing.T, bool, string, string, string) { + t.Helper() + return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp.String(), got.String() +} + +func LegacyDecApproxEq(t *testing.T, d1, d2, tol LegacyDec) (*testing.T, bool, string, string, string) { + t.Helper() + diff := d1.Sub(d2).Abs() + return t, diff.LTE(tol), "expected |d1 - d2| <:\t%v\ngot |d1 - d2| = \t\t%v", tol.String(), diff.String() +} + +// FormatDec formats a decimal (as encoded in protobuf) into a value-rendered +// string following ADR-050. This function operates with string manipulation +// (instead of manipulating the sdk.Dec object). +func FormatDec(v string) (string, error) { + parts := strings.Split(v, ".") + if len(parts) > 2 { + return "", fmt.Errorf("invalid decimal: too many points in %s", v) + } + + intPart, err := FormatInt(parts[0]) + if err != nil { + return "", err + } + + if len(parts) == 1 { + return intPart, nil + } + + decPart := strings.TrimRight(parts[1], "0") + if len(decPart) == 0 { + return intPart, nil + } + + // Ensure that the decimal part has only digits. + // https://github.com/cosmos/cosmos-sdk/issues/12811 + if !hasOnlyDigits(decPart) { + return "", fmt.Errorf("non-digits detected after decimal point in: %q", decPart) + } + + return intPart + "." + decPart, nil +} + +func LegacyDecToDec(ld LegacyDec) (Dec, error) { + return NewDecFromString(ld.String()) +} diff --git a/math/dec_legacy_test.go b/math/dec_legacy_test.go new file mode 100644 index 000000000000..7e67209e2370 --- /dev/null +++ b/math/dec_legacy_test.go @@ -0,0 +1,1028 @@ +package math_test + +import ( + "bytes" + "encoding/json" + "fmt" + "math/big" + "os" + "strings" + "testing" + + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "sigs.k8s.io/yaml" + + "cosmossdk.io/math" +) + +type decimalTestSuite struct { + suite.Suite +} + +func TestDecimalTestSuite(t *testing.T) { + suite.Run(t, new(decimalTestSuite)) +} + +func TestDecApproxEq(t *testing.T) { + // d1 = 0.55, d2 = 0.6, tol = 0.1 + d1 := math.LegacyNewDecWithPrec(55, 2) + d2 := math.LegacyNewDecWithPrec(6, 1) + tol := math.LegacyNewDecWithPrec(1, 1) + + require.True(math.LegacyDecApproxEq(t, d1, d2, tol)) + + // d1 = 0.55, d2 = 0.6, tol = 1E-5 + d1 = math.LegacyNewDecWithPrec(55, 2) + d2 = math.LegacyNewDecWithPrec(6, 1) + tol = math.LegacyNewDecWithPrec(1, 5) + + require.False(math.LegacyDecApproxEq(t, d1, d2, tol)) + + // d1 = 0.6, d2 = 0.61, tol = 0.01 + d1 = math.LegacyNewDecWithPrec(6, 1) + d2 = math.LegacyNewDecWithPrec(61, 2) + tol = math.LegacyNewDecWithPrec(1, 2) + + require.True(math.LegacyDecApproxEq(t, d1, d2, tol)) +} + +// create a decimal from a decimal string (ex. "1234.5678") +func (s *decimalTestSuite) mustNewDecFromStr(str string) (d math.LegacyDec) { + d, err := math.LegacyNewDecFromStr(str) + s.Require().NoError(err) + + return d +} + +func (s *decimalTestSuite) TestNewDecFromStr() { + largeBigInt, ok := new(big.Int).SetString("3144605511029693144278234343371835", 10) + s.Require().True(ok) + + largerBigInt, ok := new(big.Int).SetString("8888888888888888888888888888888888888888888888888888888888888888888844444440", 10) + s.Require().True(ok) + + largestBigInt, ok := new(big.Int).SetString("33499189745056880149688856635597007162669032647290798121690100488888732861290034376435130433535", 10) + s.Require().True(ok) + + tests := []struct { + decimalStr string + expErr bool + exp math.LegacyDec + }{ + {"", true, math.LegacyDec{}}, + {"0.-75", true, math.LegacyDec{}}, + {"0", false, math.LegacyNewDec(0)}, + {"1", false, math.LegacyNewDec(1)}, + {"1.1", false, math.LegacyNewDecWithPrec(11, 1)}, + {"0.75", false, math.LegacyNewDecWithPrec(75, 2)}, + {"0.8", false, math.LegacyNewDecWithPrec(8, 1)}, + {"0.11111", false, math.LegacyNewDecWithPrec(11111, 5)}, + {"314460551102969.3144278234343371835", true, math.LegacyNewDec(3141203149163817869)}, + { + "314460551102969314427823434337.1835718092488231350", + true, math.LegacyNewDecFromBigIntWithPrec(largeBigInt, 4), + }, + { + "314460551102969314427823434337.1835", + false, math.LegacyNewDecFromBigIntWithPrec(largeBigInt, 4), + }, + {".", true, math.LegacyDec{}}, + {".0", true, math.LegacyNewDec(0)}, + {"1.", true, math.LegacyNewDec(1)}, + {"foobar", true, math.LegacyDec{}}, + {"0.foobar", true, math.LegacyDec{}}, + {"0.foobar.", true, math.LegacyDec{}}, + {"8888888888888888888888888888888888888888888888888888888888888888888844444440", false, math.LegacyNewDecFromBigInt(largerBigInt)}, + {"33499189745056880149688856635597007162669032647290798121690100488888732861290.034376435130433535", false, math.LegacyNewDecFromBigIntWithPrec(largestBigInt, 18)}, + {"133499189745056880149688856635597007162669032647290798121690100488888732861291", true, math.LegacyDec{}}, + } + + for tcIndex, tc := range tests { + res, err := math.LegacyNewDecFromStr(tc.decimalStr) + if tc.expErr { + s.Require().NotNil(err, "error expected, decimalStr %v, tc %v", tc.decimalStr, tcIndex) + } else { + s.Require().Nil(err, "unexpected error, decimalStr %v, tc %v", tc.decimalStr, tcIndex) + s.Require().True(res.Equal(tc.exp), "equality was incorrect, res %v, exp %v, tc %v", res, tc.exp, tcIndex) + } + + // negative tc + res, err = math.LegacyNewDecFromStr("-" + tc.decimalStr) + if tc.expErr { + s.Require().NotNil(err, "error expected, decimalStr %v, tc %v", tc.decimalStr, tcIndex) + } else { + s.Require().Nil(err, "unexpected error, decimalStr %v, tc %v", tc.decimalStr, tcIndex) + exp := tc.exp.Mul(math.LegacyNewDec(-1)) + s.Require().True(res.Equal(exp), "equality was incorrect, res %v, exp %v, tc %v", res, exp, tcIndex) + } + } +} + +func (s *decimalTestSuite) TestDecString() { + tests := []struct { + d math.LegacyDec + want string + }{ + {math.LegacyNewDec(0), "0.000000000000000000"}, + {math.LegacyNewDec(1), "1.000000000000000000"}, + {math.LegacyNewDec(10), "10.000000000000000000"}, + {math.LegacyNewDec(12340), "12340.000000000000000000"}, + {math.LegacyNewDecWithPrec(12340, 4), "1.234000000000000000"}, + {math.LegacyNewDecWithPrec(12340, 5), "0.123400000000000000"}, + {math.LegacyNewDecWithPrec(12340, 8), "0.000123400000000000"}, + {math.LegacyNewDecWithPrec(1009009009009009009, 17), "10.090090090090090090"}, + } + for tcIndex, tc := range tests { + s.Require().Equal(tc.want, tc.d.String(), "bad String(), index: %v", tcIndex) + } +} + +func (s *decimalTestSuite) TestDecFloat64() { + tests := []struct { + d math.LegacyDec + want float64 + }{ + {math.LegacyNewDec(0), 0.000000000000000000}, + {math.LegacyNewDec(1), 1.000000000000000000}, + {math.LegacyNewDec(10), 10.000000000000000000}, + {math.LegacyNewDec(12340), 12340.000000000000000000}, + {math.LegacyNewDecWithPrec(12340, 4), 1.234000000000000000}, + {math.LegacyNewDecWithPrec(12340, 5), 0.123400000000000000}, + {math.LegacyNewDecWithPrec(12340, 8), 0.000123400000000000}, + {math.LegacyNewDecWithPrec(1009009009009009009, 17), 10.090090090090090090}, + } + for tcIndex, tc := range tests { + value, err := tc.d.Float64() + s.Require().Nil(err, "error getting Float64(), index: %v", tcIndex) + s.Require().Equal(tc.want, value, "bad Float64(), index: %v", tcIndex) + s.Require().Equal(tc.want, tc.d.MustFloat64(), "bad MustFloat64(), index: %v", tcIndex) + } +} + +func (s *decimalTestSuite) TestEqualities() { + tests := []struct { + d1, d2 math.LegacyDec + gt, lt, eq bool + }{ + {math.LegacyNewDec(0), math.LegacyNewDec(0), false, false, true}, + {math.LegacyNewDecWithPrec(0, 2), math.LegacyNewDecWithPrec(0, 4), false, false, true}, + {math.LegacyNewDecWithPrec(100, 0), math.LegacyNewDecWithPrec(100, 0), false, false, true}, + {math.LegacyNewDecWithPrec(-100, 0), math.LegacyNewDecWithPrec(-100, 0), false, false, true}, + {math.LegacyNewDecWithPrec(-1, 1), math.LegacyNewDecWithPrec(-1, 1), false, false, true}, + {math.LegacyNewDecWithPrec(3333, 3), math.LegacyNewDecWithPrec(3333, 3), false, false, true}, + + {math.LegacyNewDecWithPrec(0, 0), math.LegacyNewDecWithPrec(3333, 3), false, true, false}, + {math.LegacyNewDecWithPrec(0, 0), math.LegacyNewDecWithPrec(100, 0), false, true, false}, + {math.LegacyNewDecWithPrec(-1, 0), math.LegacyNewDecWithPrec(3333, 3), false, true, false}, + {math.LegacyNewDecWithPrec(-1, 0), math.LegacyNewDecWithPrec(100, 0), false, true, false}, + {math.LegacyNewDecWithPrec(1111, 3), math.LegacyNewDecWithPrec(100, 0), false, true, false}, + {math.LegacyNewDecWithPrec(1111, 3), math.LegacyNewDecWithPrec(3333, 3), false, true, false}, + {math.LegacyNewDecWithPrec(-3333, 3), math.LegacyNewDecWithPrec(-1111, 3), false, true, false}, + + {math.LegacyNewDecWithPrec(3333, 3), math.LegacyNewDecWithPrec(0, 0), true, false, false}, + {math.LegacyNewDecWithPrec(100, 0), math.LegacyNewDecWithPrec(0, 0), true, false, false}, + {math.LegacyNewDecWithPrec(3333, 3), math.LegacyNewDecWithPrec(-1, 0), true, false, false}, + {math.LegacyNewDecWithPrec(100, 0), math.LegacyNewDecWithPrec(-1, 0), true, false, false}, + {math.LegacyNewDecWithPrec(100, 0), math.LegacyNewDecWithPrec(1111, 3), true, false, false}, + {math.LegacyNewDecWithPrec(3333, 3), math.LegacyNewDecWithPrec(1111, 3), true, false, false}, + {math.LegacyNewDecWithPrec(-1111, 3), math.LegacyNewDecWithPrec(-3333, 3), true, false, false}, + } + + for tcIndex, tc := range tests { + s.Require().Equal(tc.gt, tc.d1.GT(tc.d2), "GT result is incorrect, tc %d", tcIndex) + s.Require().Equal(tc.lt, tc.d1.LT(tc.d2), "LT result is incorrect, tc %d", tcIndex) + s.Require().Equal(tc.eq, tc.d1.Equal(tc.d2), "equality result is incorrect, tc %d", tcIndex) + } +} + +func (s *decimalTestSuite) TestDecsEqual() { + tests := []struct { + d1s, d2s []math.LegacyDec + eq bool + }{ + {[]math.LegacyDec{math.LegacyNewDec(0)}, []math.LegacyDec{math.LegacyNewDec(0)}, true}, + {[]math.LegacyDec{math.LegacyNewDec(0)}, []math.LegacyDec{math.LegacyNewDec(1)}, false}, + {[]math.LegacyDec{math.LegacyNewDec(0)}, []math.LegacyDec{}, false}, + {[]math.LegacyDec{math.LegacyNewDec(0), math.LegacyNewDec(1)}, []math.LegacyDec{math.LegacyNewDec(0), math.LegacyNewDec(1)}, true}, + {[]math.LegacyDec{math.LegacyNewDec(1), math.LegacyNewDec(0)}, []math.LegacyDec{math.LegacyNewDec(1), math.LegacyNewDec(0)}, true}, + {[]math.LegacyDec{math.LegacyNewDec(1), math.LegacyNewDec(0)}, []math.LegacyDec{math.LegacyNewDec(0), math.LegacyNewDec(1)}, false}, + {[]math.LegacyDec{math.LegacyNewDec(1), math.LegacyNewDec(0)}, []math.LegacyDec{math.LegacyNewDec(1)}, false}, + {[]math.LegacyDec{math.LegacyNewDec(1), math.LegacyNewDec(2)}, []math.LegacyDec{math.LegacyNewDec(2), math.LegacyNewDec(4)}, false}, + {[]math.LegacyDec{math.LegacyNewDec(3), math.LegacyNewDec(18)}, []math.LegacyDec{math.LegacyNewDec(1), math.LegacyNewDec(6)}, false}, + } + + for tcIndex, tc := range tests { + s.Require().Equal(tc.eq, math.LegacyDecsEqual(tc.d1s, tc.d2s), "equality of decional arrays is incorrect, tc %d", tcIndex) + s.Require().Equal(tc.eq, math.LegacyDecsEqual(tc.d2s, tc.d1s), "equality of decional arrays is incorrect (converse), tc %d", tcIndex) + } +} + +func (s *decimalTestSuite) TestArithmetic() { + tests := []struct { + d1, d2 math.LegacyDec + expMul, expMulTruncate, expMulRoundUp math.LegacyDec + expQuo, expQuoRoundUp, expQuoTruncate math.LegacyDec + expAdd, expSub math.LegacyDec + }{ + // d1 d2 MUL MulTruncate MulRoundUp QUO QUORoundUp QUOTrunctate ADD SUB + {math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0)}, + {math.LegacyNewDec(1), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(1), math.LegacyNewDec(1)}, + {math.LegacyNewDec(0), math.LegacyNewDec(1), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(1), math.LegacyNewDec(-1)}, + {math.LegacyNewDec(0), math.LegacyNewDec(-1), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(-1), math.LegacyNewDec(1)}, + {math.LegacyNewDec(-1), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(-1), math.LegacyNewDec(-1)}, + + {math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(2), math.LegacyNewDec(0)}, + {math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(-2), math.LegacyNewDec(0)}, + {math.LegacyNewDec(1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(0), math.LegacyNewDec(2)}, + {math.LegacyNewDec(-1), math.LegacyNewDec(1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(0), math.LegacyNewDec(-2)}, + + { + math.LegacyNewDec(3), math.LegacyNewDec(7), math.LegacyNewDec(21), math.LegacyNewDec(21), math.LegacyNewDec(21), + math.LegacyNewDecWithPrec(428571428571428571, 18), math.LegacyNewDecWithPrec(428571428571428572, 18), math.LegacyNewDecWithPrec(428571428571428571, 18), + math.LegacyNewDec(10), math.LegacyNewDec(-4), + }, + { + math.LegacyNewDec(2), math.LegacyNewDec(4), math.LegacyNewDec(8), math.LegacyNewDec(8), math.LegacyNewDec(8), math.LegacyNewDecWithPrec(5, 1), math.LegacyNewDecWithPrec(5, 1), math.LegacyNewDecWithPrec(5, 1), + math.LegacyNewDec(6), math.LegacyNewDec(-2), + }, + + {math.LegacyNewDec(100), math.LegacyNewDec(100), math.LegacyNewDec(10000), math.LegacyNewDec(10000), math.LegacyNewDec(10000), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(200), math.LegacyNewDec(0)}, + + { + math.LegacyNewDecWithPrec(15, 1), math.LegacyNewDecWithPrec(15, 1), math.LegacyNewDecWithPrec(225, 2), math.LegacyNewDecWithPrec(225, 2), math.LegacyNewDecWithPrec(225, 2), + math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(3), math.LegacyNewDec(0), + }, + { + math.LegacyNewDecWithPrec(3333, 4), math.LegacyNewDecWithPrec(333, 4), math.LegacyNewDecWithPrec(1109889, 8), math.LegacyNewDecWithPrec(1109889, 8), math.LegacyNewDecWithPrec(1109889, 8), + math.LegacyMustNewDecFromStr("10.009009009009009009"), math.LegacyMustNewDecFromStr("10.009009009009009010"), math.LegacyMustNewDecFromStr("10.009009009009009009"), + math.LegacyNewDecWithPrec(3666, 4), math.LegacyNewDecWithPrec(3, 1), + }, + } + + for tcIndex, tc := range tests { + tc := tc + resAdd := tc.d1.Add(tc.d2) + resSub := tc.d1.Sub(tc.d2) + resMul := tc.d1.Mul(tc.d2) + resMulTruncate := tc.d1.MulTruncate(tc.d2) + resMulRoundUp := tc.d1.MulRoundUp(tc.d2) + s.Require().True(tc.expAdd.Equal(resAdd), "exp %v, res %v, tc %d", tc.expAdd, resAdd, tcIndex) + s.Require().True(tc.expSub.Equal(resSub), "exp %v, res %v, tc %d", tc.expSub, resSub, tcIndex) + s.Require().True(tc.expMul.Equal(resMul), "exp %v, res %v, tc %d", tc.expMul, resMul, tcIndex) + s.Require().True(tc.expMulTruncate.Equal(resMulTruncate), "exp %v, res %v, tc %d", tc.expMulTruncate, resMulTruncate, tcIndex) + s.Require().True(tc.expMulRoundUp.Equal(resMulRoundUp), "exp %v, res %v, tc %d", tc.expMulRoundUp, resMulRoundUp, tcIndex) + + if tc.d2.IsZero() { // panic for divide by zero + s.Require().Panics(func() { tc.d1.Quo(tc.d2) }) + } else { + resQuo := tc.d1.Quo(tc.d2) + s.Require().True(tc.expQuo.Equal(resQuo), "exp %v, res %v, tc %d", tc.expQuo.String(), resQuo.String(), tcIndex) + + resQuoRoundUp := tc.d1.QuoRoundUp(tc.d2) + s.Require().True(tc.expQuoRoundUp.Equal(resQuoRoundUp), "exp %v, res %v, tc %d", + tc.expQuoRoundUp.String(), resQuoRoundUp.String(), tcIndex) + + resQuoTruncate := tc.d1.QuoTruncate(tc.d2) + s.Require().True(tc.expQuoTruncate.Equal(resQuoTruncate), "exp %v, res %v, tc %d", + tc.expQuoTruncate.String(), resQuoTruncate.String(), tcIndex) + } + } +} + +func (s *decimalTestSuite) TestMulRoundUp_RoundingAtPrecisionEnd() { + var ( + a = math.LegacyMustNewDecFromStr("0.000000000000000009") + b = math.LegacyMustNewDecFromStr("0.000000000000000009") + expectedRoundUp = math.LegacyMustNewDecFromStr("0.000000000000000001") + expectedTruncate = math.LegacyMustNewDecFromStr("0.000000000000000000") + ) + + actualRoundUp := a.MulRoundUp(b) + s.Require().Equal(expectedRoundUp.String(), actualRoundUp.String(), "exp %v, res %v", expectedRoundUp, actualRoundUp) + + actualTruncate := a.MulTruncate(b) + s.Require().Equal(expectedTruncate.String(), actualTruncate.String(), "exp %v, res %v", expectedRoundUp, actualTruncate) +} + +func (s *decimalTestSuite) TestBankerRoundChop() { + tests := []struct { + d1 math.LegacyDec + exp int64 + }{ + {s.mustNewDecFromStr("0.25"), 0}, + {s.mustNewDecFromStr("0"), 0}, + {s.mustNewDecFromStr("1"), 1}, + {s.mustNewDecFromStr("0.75"), 1}, + {s.mustNewDecFromStr("0.5"), 0}, + {s.mustNewDecFromStr("7.5"), 8}, + {s.mustNewDecFromStr("1.5"), 2}, + {s.mustNewDecFromStr("2.5"), 2}, + {s.mustNewDecFromStr("0.545"), 1}, // 0.545-> 1 even though 5 is first decimal and 1 not even + {s.mustNewDecFromStr("1.545"), 2}, + } + + for tcIndex, tc := range tests { + resNeg := tc.d1.Neg().RoundInt64() + s.Require().Equal(-1*tc.exp, resNeg, "negative tc %d", tcIndex) + + resPos := tc.d1.RoundInt64() + s.Require().Equal(tc.exp, resPos, "positive tc %d", tcIndex) + } +} + +func (s *decimalTestSuite) TestTruncate() { + tests := []struct { + d1 math.LegacyDec + exp int64 + }{ + {s.mustNewDecFromStr("0"), 0}, + {s.mustNewDecFromStr("0.25"), 0}, + {s.mustNewDecFromStr("0.75"), 0}, + {s.mustNewDecFromStr("1"), 1}, + {s.mustNewDecFromStr("1.5"), 1}, + {s.mustNewDecFromStr("7.5"), 7}, + {s.mustNewDecFromStr("7.6"), 7}, + {s.mustNewDecFromStr("7.4"), 7}, + {s.mustNewDecFromStr("100.1"), 100}, + {s.mustNewDecFromStr("1000.1"), 1000}, + } + + for tcIndex, tc := range tests { + resNeg := tc.d1.Neg().TruncateInt64() + s.Require().Equal(-1*tc.exp, resNeg, "negative tc %d", tcIndex) + + resPos := tc.d1.TruncateInt64() + s.Require().Equal(tc.exp, resPos, "positive tc %d", tcIndex) + } +} + +func (s *decimalTestSuite) TestStringOverflow() { + // two random 64 bit primes + dec1, err := math.LegacyNewDecFromStr("51643150036226787134389711697696177267") + s.Require().NoError(err) + dec2, err := math.LegacyNewDecFromStr("-31798496660535729618459429845579852627") + s.Require().NoError(err) + dec3 := dec1.Add(dec2) + s.Require().Equal( + "19844653375691057515930281852116324640.000000000000000000", + dec3.String(), + ) +} + +func (s *decimalTestSuite) TestDecMulInt() { + tests := []struct { + sdkDec math.LegacyDec + sdkInt math.Int + want math.LegacyDec + }{ + {math.LegacyNewDec(10), math.NewInt(2), math.LegacyNewDec(20)}, + {math.LegacyNewDec(1000000), math.NewInt(100), math.LegacyNewDec(100000000)}, + {math.LegacyNewDecWithPrec(1, 1), math.NewInt(10), math.LegacyNewDec(1)}, + {math.LegacyNewDecWithPrec(1, 5), math.NewInt(20), math.LegacyNewDecWithPrec(2, 4)}, + } + for i, tc := range tests { + got := tc.sdkDec.MulInt(tc.sdkInt) + s.Require().Equal(tc.want, got, "Incorrect result on test case %d", i) + } +} + +func (s *decimalTestSuite) TestDecCeil() { + testCases := []struct { + input math.LegacyDec + expected math.LegacyDec + }{ + {math.LegacyNewDecWithPrec(1000000000000000, math.LegacyPrecision), math.LegacyNewDec(1)}, // 0.001 => 1.0 + {math.LegacyNewDecWithPrec(-1000000000000000, math.LegacyPrecision), math.LegacyZeroDec()}, // -0.001 => 0.0 + {math.LegacyZeroDec(), math.LegacyZeroDec()}, // 0.0 => 0.0 + {math.LegacyNewDecWithPrec(900000000000000000, math.LegacyPrecision), math.LegacyNewDec(1)}, // 0.9 => 1.0 + {math.LegacyNewDecWithPrec(4001000000000000000, math.LegacyPrecision), math.LegacyNewDec(5)}, // 4.001 => 5.0 + {math.LegacyNewDecWithPrec(-4001000000000000000, math.LegacyPrecision), math.LegacyNewDec(-4)}, // -4.001 => -4.0 + {math.LegacyNewDecWithPrec(4700000000000000000, math.LegacyPrecision), math.LegacyNewDec(5)}, // 4.7 => 5.0 + {math.LegacyNewDecWithPrec(-4700000000000000000, math.LegacyPrecision), math.LegacyNewDec(-4)}, // -4.7 => -4.0 + } + + for i, tc := range testCases { + res := tc.input.Ceil() + s.Require().Equal(tc.expected, res, "unexpected result for test case %d, input: %v", i, tc.input) + } +} + +func (s *decimalTestSuite) TestCeilOverflow() { + d, err := math.LegacyNewDecFromStr("66749594872528440074844428317798503581334516323645399060845050244444366430645.000000000000000001") + s.Require().NoError(err) + s.Require().True(d.BigInt().BitLen() <= 315, "d is too large") + // this call panics because the value is too large + s.Require().Panics(func() { d.Ceil() }, "Ceil should panic on overflow") +} + +func (s *decimalTestSuite) TestPower() { + testCases := []struct { + input math.LegacyDec + power uint64 + expected math.LegacyDec + }{ + {math.LegacyNewDec(100), 0, math.LegacyOneDec()}, // 10 ^ (0) => 1.0 + {math.LegacyOneDec(), 10, math.LegacyOneDec()}, // 1.0 ^ (10) => 1.0 + {math.LegacyNewDecWithPrec(5, 1), 2, math.LegacyNewDecWithPrec(25, 2)}, // 0.5 ^ 2 => 0.25 + {math.LegacyNewDecWithPrec(2, 1), 2, math.LegacyNewDecWithPrec(4, 2)}, // 0.2 ^ 2 => 0.04 + {math.LegacyNewDecFromInt(math.NewInt(3)), 3, math.LegacyNewDecFromInt(math.NewInt(27))}, // 3 ^ 3 => 27 + {math.LegacyNewDecFromInt(math.NewInt(-3)), 4, math.LegacyNewDecFromInt(math.NewInt(81))}, // -3 ^ 4 = 81 + {math.LegacyNewDecWithPrec(1414213562373095049, 18), 2, math.LegacyNewDecFromInt(math.NewInt(2))}, // 1.414213562373095049 ^ 2 = 2 + } + + for i, tc := range testCases { + res := tc.input.Power(tc.power) + s.Require().True(tc.expected.Sub(res).Abs().LTE(math.LegacySmallestDec()), "unexpected result for test case %d, normal power, input: %v", i, tc.input) + + mutableInput := tc.input + mutableInput.PowerMut(tc.power) + s.Require().True(tc.expected.Sub(mutableInput).Abs().LTE(math.LegacySmallestDec()), + "unexpected result for test case %d, input %v", i, tc.input) + s.Require().True(res.Equal(tc.input), "unexpected result for test case %d, mutable power, input: %v", i, tc.input) + } +} + +func (s *decimalTestSuite) TestApproxRoot() { + testCases := []struct { + input math.LegacyDec + root uint64 + expected math.LegacyDec + }{ + {math.LegacyOneDec(), 10, math.LegacyOneDec()}, // 1.0 ^ (0.1) => 1.0 + {math.LegacyNewDecWithPrec(25, 2), 2, math.LegacyNewDecWithPrec(5, 1)}, // 0.25 ^ (0.5) => 0.5 + {math.LegacyNewDecWithPrec(4, 2), 2, math.LegacyNewDecWithPrec(2, 1)}, // 0.04 ^ (0.5) => 0.2 + {math.LegacyNewDecFromInt(math.NewInt(27)), 3, math.LegacyNewDecFromInt(math.NewInt(3))}, // 27 ^ (1/3) => 3 + {math.LegacyNewDecFromInt(math.NewInt(-81)), 4, math.LegacyNewDecFromInt(math.NewInt(-3))}, // -81 ^ (0.25) => -3 + {math.LegacyNewDecFromInt(math.NewInt(2)), 2, math.LegacyNewDecWithPrec(1414213562373095049, 18)}, // 2 ^ (0.5) => 1.414213562373095049 + {math.LegacyNewDecWithPrec(1005, 3), 31536000, math.LegacyMustNewDecFromStr("1.000000000158153904")}, // 1.005 ^ (1/31536000) ≈ 1.00000000016 + {math.LegacySmallestDec(), 2, math.LegacyNewDecWithPrec(1, 9)}, // 1e-18 ^ (0.5) => 1e-9 + {math.LegacySmallestDec(), 3, math.LegacyMustNewDecFromStr("0.000000999999999997")}, // 1e-18 ^ (1/3) => 1e-6 + {math.LegacyNewDecWithPrec(1, 8), 3, math.LegacyMustNewDecFromStr("0.002154434690031900")}, // 1e-8 ^ (1/3) ≈ 0.00215443469 + {math.LegacyMustNewDecFromStr("9000002314687921634000000000000000000021394871242000000000000000"), 2, math.LegacyMustNewDecFromStr("94868342004527103646332858502867.899477053226766107")}, + } + + // In the case of 1e-8 ^ (1/3), the result repeats every 5 iterations starting from iteration 24 + // (i.e. 24, 29, 34, ... give the same result) and never converges enough. The maximum number of + // iterations (300) causes the result at iteration 300 to be returned, regardless of convergence. + + for i, tc := range testCases { + res, err := tc.input.ApproxRoot(tc.root) + s.Require().NoError(err) + s.Require().True(tc.expected.Sub(res).Abs().LTE(math.LegacySmallestDec()), "unexpected result for test case %d, input: %v", i, tc.input) + } +} + +func (s *decimalTestSuite) TestApproxSqrt() { + testCases := []struct { + input math.LegacyDec + expected math.LegacyDec + }{ + {math.LegacyOneDec(), math.LegacyOneDec()}, // 1.0 => 1.0 + {math.LegacyNewDecWithPrec(25, 2), math.LegacyNewDecWithPrec(5, 1)}, // 0.25 => 0.5 + {math.LegacyNewDecWithPrec(4, 2), math.LegacyNewDecWithPrec(2, 1)}, // 0.09 => 0.3 + {math.LegacyNewDec(9), math.LegacyNewDecFromInt(math.NewInt(3))}, // 9 => 3 + {math.LegacyNewDec(-9), math.LegacyNewDecFromInt(math.NewInt(-3))}, // -9 => -3 + {math.LegacyNewDec(2), math.LegacyNewDecWithPrec(1414213562373095049, 18)}, // 2 => 1.414213562373095049 + { // 2^127 - 1 => 13043817825332782212.3495718062525083688 which rounds to 13043817825332782212.3495718062525083689 + math.LegacyNewDec(2).Power(127).Sub(math.LegacyOneDec()), + math.LegacyMustNewDecFromStr("13043817825332782212.349571806252508369"), + }, + {math.LegacyMustNewDecFromStr("1.000000011823380862"), math.LegacyMustNewDecFromStr("1.000000005911690414")}, + } + + for i, tc := range testCases { + res, err := tc.input.ApproxSqrt() + s.Require().NoError(err) + s.Require().Equal(tc.expected, res, "unexpected result for test case %d, input: %v", i, tc.input) + } +} + +func (s *decimalTestSuite) TestDecSortableBytes() { + tests := []struct { + d math.LegacyDec + want []byte + }{ + {math.LegacyNewDec(0), []byte("000000000000000000.000000000000000000")}, + {math.LegacyNewDec(1), []byte("000000000000000001.000000000000000000")}, + {math.LegacyNewDec(10), []byte("000000000000000010.000000000000000000")}, + {math.LegacyNewDec(12340), []byte("000000000000012340.000000000000000000")}, + {math.LegacyNewDecWithPrec(12340, 4), []byte("000000000000000001.234000000000000000")}, + {math.LegacyNewDecWithPrec(12340, 5), []byte("000000000000000000.123400000000000000")}, + {math.LegacyNewDecWithPrec(12340, 8), []byte("000000000000000000.000123400000000000")}, + {math.LegacyNewDecWithPrec(1009009009009009009, 17), []byte("000000000000000010.090090090090090090")}, + {math.LegacyNewDecWithPrec(-1009009009009009009, 17), []byte("-000000000000000010.090090090090090090")}, + {math.LegacyNewDec(1000000000000000000), []byte("max")}, + {math.LegacyNewDec(-1000000000000000000), []byte("--")}, + } + for tcIndex, tc := range tests { + s.Require().Equal(tc.want, math.LegacySortableDecBytes(tc.d), "bad String(), index: %v", tcIndex) + } + + s.Require().Panics(func() { math.LegacySortableDecBytes(math.LegacyNewDec(1000000000000000001)) }) + s.Require().Panics(func() { math.LegacySortableDecBytes(math.LegacyNewDec(-1000000000000000001)) }) +} + +func (s *decimalTestSuite) TestDecEncoding() { + largestBigInt, ok := new(big.Int).SetString("33499189745056880149688856635597007162669032647290798121690100488888732861290034376435130433535", 10) + s.Require().True(ok) + + smallestBigInt, ok := new(big.Int).SetString("-33499189745056880149688856635597007162669032647290798121690100488888732861290034376435130433535", 10) + s.Require().True(ok) + + const maxDecBitLen = 315 + maxInt, ok := new(big.Int).SetString(strings.Repeat("1", maxDecBitLen), 2) + s.Require().True(ok) + + testCases := []struct { + input math.LegacyDec + rawBz string + jsonStr string + yamlStr string + }{ + { + math.LegacyNewDec(0), "30", + "\"0.000000000000000000\"", + "\"0.000000000000000000\"\n", + }, + { + math.LegacyNewDecWithPrec(4, 2), + "3430303030303030303030303030303030", + "\"0.040000000000000000\"", + "\"0.040000000000000000\"\n", + }, + { + math.LegacyNewDecWithPrec(-4, 2), + "2D3430303030303030303030303030303030", + "\"-0.040000000000000000\"", + "\"-0.040000000000000000\"\n", + }, + { + math.LegacyNewDecWithPrec(1414213562373095049, 18), + "31343134323133353632333733303935303439", + "\"1.414213562373095049\"", + "\"1.414213562373095049\"\n", + }, + { + math.LegacyNewDecWithPrec(-1414213562373095049, 18), + "2D31343134323133353632333733303935303439", + "\"-1.414213562373095049\"", + "\"-1.414213562373095049\"\n", + }, + { + math.LegacyNewDecFromBigIntWithPrec(largestBigInt, 18), + "3333343939313839373435303536383830313439363838383536363335353937303037313632363639303332363437323930373938313231363930313030343838383838373332383631323930303334333736343335313330343333353335", + "\"33499189745056880149688856635597007162669032647290798121690100488888732861290.034376435130433535\"", + "\"33499189745056880149688856635597007162669032647290798121690100488888732861290.034376435130433535\"\n", + }, + { + math.LegacyNewDecFromBigIntWithPrec(smallestBigInt, 18), + "2D3333343939313839373435303536383830313439363838383536363335353937303037313632363639303332363437323930373938313231363930313030343838383838373332383631323930303334333736343335313330343333353335", + "\"-33499189745056880149688856635597007162669032647290798121690100488888732861290.034376435130433535\"", + "\"-33499189745056880149688856635597007162669032647290798121690100488888732861290.034376435130433535\"\n", + }, + { + math.LegacyNewDecFromBigIntWithPrec(maxInt, 18), + "3636373439353934383732353238343430303734383434343238333137373938353033353831333334353136333233363435333939303630383435303530323434343434333636343330363435303137313838323137353635323136373637", + "\"66749594872528440074844428317798503581334516323645399060845050244444366430645.017188217565216767\"", + "\"66749594872528440074844428317798503581334516323645399060845050244444366430645.017188217565216767\"\n", + }, + } + + for _, tc := range testCases { + bz, err := tc.input.Marshal() + s.Require().NoError(err) + s.Require().Equal(tc.rawBz, fmt.Sprintf("%X", bz)) + + var other math.LegacyDec + s.Require().NoError((&other).Unmarshal(bz)) + s.Require().True(tc.input.Equal(other)) + + bz, err = json.Marshal(tc.input) + s.Require().NoError(err) + s.Require().Equal(tc.jsonStr, string(bz)) + s.Require().NoError(json.Unmarshal(bz, &other)) + s.Require().True(tc.input.Equal(other)) + + bz, err = yaml.Marshal(tc.input) + s.Require().NoError(err) + s.Require().Equal(tc.yamlStr, string(bz)) + } +} + +// Showcase that different orders of operations causes different results. +func (s *decimalTestSuite) TestOperationOrders() { + n1 := math.LegacyNewDec(10) + n2 := math.LegacyNewDec(1000000010) + s.Require().Equal(n1.Mul(n2).Quo(n2), math.LegacyNewDec(10)) + s.Require().NotEqual(n1.Mul(n2).Quo(n2), n1.Quo(n2).Mul(n2)) +} + +func BenchmarkMarshalTo(b *testing.B) { + b.ReportAllocs() + bis := []struct { + in math.LegacyDec + want []byte + }{ + { + math.LegacyNewDec(1e8), []byte{ + 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + }, + }, + {math.LegacyNewDec(0), []byte{0x30}}, + } + data := make([]byte, 100) + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + for _, bi := range bis { + if n, err := bi.in.MarshalTo(data); err != nil { + b.Fatal(err) + } else if !bytes.Equal(data[:n], bi.want) { + b.Fatalf("Mismatch\nGot: % x\nWant: % x\n", data[:n], bi.want) + } + } + } +} + +var sink interface{} + +func BenchmarkLegacyQuoMut(b *testing.B) { + b1 := math.LegacyNewDec(17e2 + 8371) + b2 := math.LegacyNewDec(4371) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + sink = b1.QuoMut(b2) + } + + if sink == nil { + b.Fatal("Benchmark did not run") + } + sink = (interface{})(nil) +} + +func BenchmarkLegacyQuoTruncateMut(b *testing.B) { + b1 := math.LegacyNewDec(17e2 + 8371) + baseArr := make([]math.LegacyDec, b.N) + for i := 0; i < b.N; i++ { + baseArr[i] = b1.Clone() + } + b2 := math.LegacyNewDec(4371) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + sink = baseArr[i].QuoTruncateMut(b2) + } + + if sink == nil { + b.Fatal("Benchmark did not run") + } + sink = (interface{})(nil) +} + +func BenchmarkLegacySqrtOnMersennePrime(b *testing.B) { + b1 := math.LegacyNewDec(2).Power(127).Sub(math.LegacyOneDec()) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + sink, _ = b1.ApproxSqrt() + } + + if sink == nil { + b.Fatal("Benchmark did not run") + } + sink = (interface{})(nil) +} + +func BenchmarkLegacyQuoRoundupMut(b *testing.B) { + b1 := math.LegacyNewDec(17e2 + 8371) + baseArr := make([]math.LegacyDec, b.N) + for i := 0; i < b.N; i++ { + baseArr[i] = b1.Clone() + } + b2 := math.LegacyNewDec(4371) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + sink = baseArr[i].QuoRoundupMut(b2) + } + + if sink == nil { + b.Fatal("Benchmark did not run") + } + sink = (interface{})(nil) +} + +func TestFormatDec(t *testing.T) { + type decimalTest []string + var testcases []decimalTest + raw, err := os.ReadFile("./testdata/decimals.json") + require.NoError(t, err) + err = json.Unmarshal(raw, &testcases) + require.NoError(t, err) + + for _, tc := range testcases { + tc := tc + t.Run(tc[0], func(t *testing.T) { + out, err := math.FormatDec(tc[0]) + require.NoError(t, err) + require.Equal(t, tc[1], out) + }) + } +} + +func TestFormatDecNonDigits(t *testing.T) { + badCases := []string{ + "10.a", + "1a.10", + "p1a10.", + "0.10p", + "--10", + "12.😎😎", + "11111111111133333333333333333333333333333a", + "11111111111133333333333333333333333333333 192892", + } + + for _, value := range badCases { + value := value + t.Run(value, func(t *testing.T) { + s, err := math.FormatDec(value) + if err == nil { + t.Fatal("Expected an error") + } + if g, w := err.Error(), "non-digits"; !strings.Contains(g, w) { + t.Errorf("Error mismatch\nGot: %q\nWant substring: %q", g, w) + } + if s != "" { + t.Fatalf("Got a non-empty string: %q", s) + } + }) + } +} + +func TestNegativePrecisionPanic(t *testing.T) { + require.Panics(t, func() { + math.LegacyNewDecWithPrec(10, -1) + }) +} + +func (s *decimalTestSuite) TestConvertToBigIntMutativeForLegacyDec() { + r := big.NewInt(30) + i := math.LegacyNewDecFromBigInt(r) + + // Compare value of BigInt & BigIntMut + s.Require().Equal(i.BigInt(), i.BigIntMut()) + + // Modify BigIntMut() pointer and ensure i.BigIntMut() & i.BigInt() change + p1 := i.BigIntMut() + p1.SetInt64(40) + s.Require().Equal(big.NewInt(40), i.BigIntMut()) + s.Require().Equal(big.NewInt(40), i.BigInt()) + + // Modify big.Int() pointer and ensure i.BigIntMut() & i.BigInt() don't change + p2 := i.BigInt() + p2.SetInt64(50) + s.Require().NotEqual(big.NewInt(50), i.BigIntMut()) + s.Require().NotEqual(big.NewInt(50), i.BigInt()) +} + +func TestQuoMut(t *testing.T) { + specs := map[string]struct { + dividend, divisor math.LegacyDec + expTruncated, expRoundedUp string + expPanic bool + }{ + "0.0000000000000000001": { + dividend: math.LegacyNewDecWithPrec(1, 18), + divisor: math.LegacyMustNewDecFromStr("10"), + expRoundedUp: "0.000000000000000001", + expTruncated: "0.000000000000000000", + }, + "0.0000000000000000002": { + dividend: math.LegacyNewDecWithPrec(1, 18), + divisor: math.LegacyMustNewDecFromStr("5"), + expRoundedUp: "0.000000000000000001", + expTruncated: "0.000000000000000000", + }, + "0.0000000000000000003": { + dividend: math.LegacyNewDecWithPrec(1, 18), + divisor: math.LegacyMustNewDecFromStr("3.333333333333333"), + expRoundedUp: "0.000000000000000001", + expTruncated: "0.000000000000000000", + }, + "0.0000000000000000004": { + dividend: math.LegacyNewDecWithPrec(1, 18), + divisor: math.LegacyMustNewDecFromStr("2.5"), + expRoundedUp: "0.000000000000000001", + expTruncated: "0.000000000000000000", + }, + "0.0000000000000000005": { + dividend: math.LegacyNewDecWithPrec(1, 18), + divisor: math.LegacyMustNewDecFromStr("2"), + expRoundedUp: "0.000000000000000001", + + expTruncated: "0.000000000000000000", + }, + "0.0000000000000000006": { + dividend: math.LegacyNewDecWithPrec(1, 18), + divisor: math.LegacyMustNewDecFromStr("1.666666666666666666"), + expRoundedUp: "0.000000000000000001", + + expTruncated: "0.000000000000000000", + }, + "0.0000000000000000007": { + dividend: math.LegacyNewDecWithPrec(1, 18), + divisor: math.LegacyMustNewDecFromStr("1.428571428571429"), + expRoundedUp: "0.000000000000000001", + + expTruncated: "0.000000000000000000", + }, + "0.0000000000000000008": { + dividend: math.LegacyNewDecWithPrec(1, 18), + divisor: math.LegacyMustNewDecFromStr("1.25"), + expRoundedUp: "0.000000000000000001", + + expTruncated: "0.000000000000000000", + }, + "0.0000000000000000009": { + dividend: math.LegacyNewDecWithPrec(1, 18), + divisor: math.LegacyMustNewDecFromStr("1.111111111111111"), + expRoundedUp: "0.000000000000000001", + + expTruncated: "0.000000000000000000", + }, + "-0.0000000000000000001": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("10"), + expRoundedUp: "0.000000000000000000", + expTruncated: "0.000000000000000000", + }, + "-0.0000000000000000002": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("5"), + expRoundedUp: "0.000000000000000000", + expTruncated: "0.000000000000000000", + }, + "-0.0000000000000000003": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("3.333333333333333"), + expRoundedUp: "0.000000000000000000", + expTruncated: "0.000000000000000000", + }, + "-0.0000000000000000004": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("2.5"), + expRoundedUp: "0.000000000000000000", + expTruncated: "0.000000000000000000", + }, + "-0.0000000000000000005": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("2"), + expRoundedUp: "0.000000000000000000", + expTruncated: "0.000000000000000000", + }, + "-0.0000000000000000006": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("1.666666666666666666"), + expRoundedUp: "0.000000000000000000", + expTruncated: "0.000000000000000000", + }, + "-0.0000000000000000007": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("1.428571428571429"), + expRoundedUp: "0.000000000000000000", + expTruncated: "0.000000000000000000", + }, + "-0.0000000000000000008": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("1.25"), + expRoundedUp: "0.000000000000000000", + expTruncated: "0.000000000000000000", + }, + "-0.0000000000000000009": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("1.111111111111111"), + expRoundedUp: "0.000000000000000000", + expTruncated: "0.000000000000000000", + }, + "--0.0000000000000000001": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("-10"), + expRoundedUp: "0.000000000000000001", + expTruncated: "0.000000000000000000", + }, + "--0.0000000000000000002": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("-5"), + expRoundedUp: "0.000000000000000001", + expTruncated: "0.000000000000000000", + }, + "--0.0000000000000000003": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("-3.333333333333333"), + expRoundedUp: "0.000000000000000001", + expTruncated: "0.000000000000000000", + }, + "--0.0000000000000000004": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("-2.5"), + expRoundedUp: "0.000000000000000001", + expTruncated: "0.000000000000000000", + }, + "--0.0000000000000000005": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("-2"), + expRoundedUp: "0.000000000000000001", + expTruncated: "0.000000000000000000", + }, + "--0.0000000000000000006": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("-1.666666666666666666"), + expRoundedUp: "0.000000000000000001", + expTruncated: "0.000000000000000000", + }, + "--0.0000000000000000007": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("-1.428571428571429"), + expRoundedUp: "0.000000000000000001", + expTruncated: "0.000000000000000000", + }, + "--0.0000000000000000008": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("-1.25"), + expRoundedUp: "0.000000000000000001", + expTruncated: "0.000000000000000000", + }, + "--0.0000000000000000009": { + dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), + divisor: math.LegacyMustNewDecFromStr("-1.111111111111111"), + expRoundedUp: "0.000000000000000001", + expTruncated: "0.000000000000000000", + }, + "big / small": { + dividend: math.LegacyMustNewDecFromStr("999999999999999999"), + divisor: math.LegacyNewDecWithPrec(1, 18), + expRoundedUp: "999999999999999999000000000000000000.000000000000000000", + expTruncated: "999999999999999999000000000000000000.000000000000000000", + }, + "divide by dividend": { + dividend: math.LegacyNewDecWithPrec(123, 0), + divisor: math.LegacyMustNewDecFromStr("123"), + expRoundedUp: "1.000000000000000000", + expTruncated: "1.000000000000000000", + }, + "zero divided": { + dividend: math.LegacyNewDecWithPrec(0, 0), + divisor: math.LegacyMustNewDecFromStr("1"), + expRoundedUp: "0.000000000000000000", + expTruncated: "0.000000000000000000", + }, + "zero divided by negative value": { + dividend: math.LegacyNewDecWithPrec(0, 0), + divisor: math.LegacyMustNewDecFromStr("-1"), + expRoundedUp: "0.000000000000000000", + expTruncated: "0.000000000000000000", + }, + "zero divided by zero": { + dividend: math.LegacyNewDecWithPrec(0, 0), + divisor: math.LegacyMustNewDecFromStr("0"), + expPanic: true, + }, + "divide by zero": { + dividend: math.LegacyNewDecWithPrec(1, 0), + divisor: math.LegacyMustNewDecFromStr("0"), + expPanic: true, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + t.Run("round up", func(t *testing.T) { + t.Parallel() + if !spec.expPanic { + got := spec.dividend.Clone().QuoRoundupMut(spec.divisor.Clone()) + require.Equal(t, spec.expRoundedUp, got.String()) + return + } + require.Panics(t, func() { + _ = spec.dividend.Clone().QuoRoundupMut(spec.divisor.Clone()) + }) + }) + t.Run("truncate", func(t *testing.T) { + t.Parallel() + if !spec.expPanic { + got := spec.dividend.Clone().QuoTruncateMut(spec.divisor.Clone()) + require.Equal(t, spec.expTruncated, got.String()) + return + } + require.Panics(t, func() { + _ = spec.dividend.Clone().QuoTruncateMut(spec.divisor.Clone()) + }) + }) + }) + } +} diff --git a/math/dec_rapid_test.go b/math/dec_rapid_test.go new file mode 100644 index 000000000000..27d6ba441f86 --- /dev/null +++ b/math/dec_rapid_test.go @@ -0,0 +1,598 @@ +package math + +import ( + "fmt" + "regexp" + "strconv" + "strings" + "testing" + + "github.com/stretchr/testify/require" + "pgregory.net/rapid" +) + +// Rapid is a Go library for property-based testing. +func TestDecWithRapid(t *testing.T) { + // Property tests + t.Run("TestNewDecFromInt64", rapid.MakeCheck(testDecInt64)) + + // Properties about *FromString functions + t.Run("TestInvalidNewDecFromString", rapid.MakeCheck(testInvalidNewDecFromString)) + t.Run("TestInvalidNewNonNegativeDecFromString", rapid.MakeCheck(testInvalidNewNonNegativeDecFromString)) + t.Run("TestInvalidNewNonNegativeFixedDecFromString", rapid.MakeCheck(testInvalidNewNonNegativeFixedDecFromString)) + t.Run("TestInvalidNewPositiveDecFromString", rapid.MakeCheck(testInvalidNewPositiveDecFromString)) + t.Run("TestInvalidNewPositiveFixedDecFromString", rapid.MakeCheck(testInvalidNewPositiveFixedDecFromString)) + + // Properties about addition + t.Run("TestAddLeftIdentity", rapid.MakeCheck(testAddLeftIdentity)) + t.Run("TestAddRightIdentity", rapid.MakeCheck(testAddRightIdentity)) + t.Run("TestAddCommutative", rapid.MakeCheck(testAddCommutative)) + t.Run("TestAddAssociative", rapid.MakeCheck(testAddAssociative)) + + // Properties about subtraction + t.Run("TestSubRightIdentity", rapid.MakeCheck(testSubRightIdentity)) + t.Run("TestSubZero", rapid.MakeCheck(testSubZero)) + + // Properties about multiplication + t.Run("TestMulLeftIdentity", rapid.MakeCheck(testMulLeftIdentity)) + t.Run("TestMulRightIdentity", rapid.MakeCheck(testMulRightIdentity)) + t.Run("TestMulCommutative", rapid.MakeCheck(testMulCommutative)) + t.Run("TestMulAssociative", rapid.MakeCheck(testMulAssociative)) + t.Run("TestZeroIdentity", rapid.MakeCheck(testMulZero)) + + // Properties about division + t.Run("TestDivisionBySelf", rapid.MakeCheck(testSelfQuo)) + t.Run("TestDivisionByOne", rapid.MakeCheck(testQuoByOne)) + + // Properties combining operations + t.Run("TestSubAdd", rapid.MakeCheck(testSubAdd)) + t.Run("TestAddSub", rapid.MakeCheck(testAddSub)) + t.Run("TestMulQuoA", rapid.MakeCheck(testMulQuoA)) + t.Run("TestMulQuoB", rapid.MakeCheck(testMulQuoB)) + t.Run("TestMulQuoExact", rapid.MakeCheck(testMulQuoExact)) + t.Run("TestQuoMulExact", rapid.MakeCheck(testQuoMulExact)) + + // Properties about comparison and equality + t.Run("TestCmpInverse", rapid.MakeCheck(testCmpInverse)) + t.Run("TestEqualCommutative", rapid.MakeCheck(testEqualCommutative)) + + // Properties about tests on a single Dec + t.Run("TestIsZero", rapid.MakeCheck(testIsZero)) + t.Run("TestIsNegative", rapid.MakeCheck(testIsNegative)) + t.Run("TestIsPositive", rapid.MakeCheck(testIsPositive)) + t.Run("TestNumDecimalPlaces", rapid.MakeCheck(testNumDecimalPlaces)) + + // Unit tests + zero := Dec{} + one := NewDecFromInt64(1) + two := NewDecFromInt64(2) + three := NewDecFromInt64(3) + four := NewDecFromInt64(4) + five := NewDecFromInt64(5) + minusOne := NewDecFromInt64(-1) + + onePointOneFive, err := NewDecFromString("1.15") + require.NoError(t, err) + twoPointThreeFour, err := NewDecFromString("2.34") + require.NoError(t, err) + threePointFourNine, err := NewDecFromString("3.49") + require.NoError(t, err) + onePointFourNine, err := NewDecFromString("1.49") + require.NoError(t, err) + minusFivePointZero, err := NewDecFromString("-5.0") + require.NoError(t, err) + + twoThousand := NewDecWithPrec(2, 3) + require.True(t, twoThousand.Equal(NewDecFromInt64(2000))) + + res, err := two.Add(zero) + require.NoError(t, err) + require.True(t, res.Equal(two)) + + res, err = five.Sub(two) + require.NoError(t, err) + require.True(t, res.Equal(three)) + + res, err = SafeSubBalance(five, two) + require.NoError(t, err) + require.True(t, res.Equal(three)) + + _, err = SafeSubBalance(two, five) + require.Error(t, err, "Expected insufficient funds error") + + res, err = SafeAddBalance(three, two) + require.NoError(t, err) + require.True(t, res.Equal(five)) + + _, err = SafeAddBalance(minusFivePointZero, five) + require.Error(t, err, "Expected ErrInvalidRequest") + + res, err = four.Quo(two) + require.NoError(t, err) + require.True(t, res.Equal(two)) + + res, err = five.QuoInteger(two) + require.NoError(t, err) + require.True(t, res.Equal(two)) + + res, err = five.Rem(two) + require.NoError(t, err) + require.True(t, res.Equal(one)) + + x, err := four.Int64() + require.NoError(t, err) + require.Equal(t, int64(4), x) + + require.Equal(t, "5", five.String()) + + res, err = onePointOneFive.Add(twoPointThreeFour) + require.NoError(t, err) + require.True(t, res.Equal(threePointFourNine)) + + res, err = threePointFourNine.Sub(two) + require.NoError(t, err) + require.True(t, res.Equal(onePointFourNine)) + + res, err = minusOne.Sub(four) + require.NoError(t, err) + require.True(t, res.Equal(minusFivePointZero)) + + require.True(t, zero.IsZero()) + require.False(t, zero.IsPositive()) + require.False(t, zero.IsNegative()) + + require.False(t, one.IsZero()) + require.True(t, one.IsPositive()) + require.False(t, one.IsNegative()) + + require.False(t, minusOne.IsZero()) + require.False(t, minusOne.IsPositive()) + require.True(t, minusOne.IsNegative()) + + res, err = one.MulExact(two) + require.NoError(t, err) + require.True(t, res.Equal(two)) +} + +var genDec *rapid.Generator[Dec] = rapid.Custom(func(t *rapid.T) Dec { + f := rapid.Float64().Draw(t, "f") + dec, err := NewDecFromString(fmt.Sprintf("%g", f)) + require.NoError(t, err) + return dec +}) + +// A Dec value and the float used to create it +type floatAndDec struct { + float float64 + dec Dec +} + +// Generate a Dec value along with the float used to create it +var genFloatAndDec *rapid.Generator[floatAndDec] = rapid.Custom(func(t *rapid.T) floatAndDec { + f := rapid.Float64().Draw(t, "f") + dec, err := NewDecFromString(fmt.Sprintf("%g", f)) + require.NoError(t, err) + return floatAndDec{f, dec} +}) + +// Property: n == NewDecFromInt64(n).Int64() +func testDecInt64(t *rapid.T) { + nIn := rapid.Int64().Draw(t, "n") + nOut, err := NewDecFromInt64(nIn).Int64() + + require.NoError(t, err) + require.Equal(t, nIn, nOut) +} + +// Property: invalid_number_string(s) => NewDecFromString(s) == err +func testInvalidNewDecFromString(t *rapid.T) { + s := rapid.StringMatching("[[:alpha:]]+").Draw(t, "s") + _, err := NewDecFromString(s) + require.Error(t, err) +} + +// Property: invalid_number_string(s) || IsNegative(s) +// => NewNonNegativeDecFromString(s) == err +func testInvalidNewNonNegativeDecFromString(t *rapid.T) { + s := rapid.OneOf( + rapid.StringMatching("[[:alpha:]]+"), + rapid.StringMatching(`^-\d*\.?\d+$`).Filter( + func(s string) bool { return !strings.HasPrefix(s, "-0") && !strings.HasPrefix(s, "-.0") }, + ), + ).Draw(t, "s") + _, err := NewDecFromString(s, AssertNotNegative()) + require.Error(t, err) +} + +// Property: invalid_number_string(s) || IsNegative(s) || NumDecimals(s) > n +// => NewNonNegativeFixedDecFromString(s, n) == err +func testInvalidNewNonNegativeFixedDecFromString(t *rapid.T) { + n := rapid.Uint32Range(0, 999).Draw(t, "n") + s := rapid.OneOf( + rapid.StringMatching("[[:alpha:]]+"), + rapid.StringMatching(`^-\d*\.?\d+$`).Filter( + func(s string) bool { return !strings.HasPrefix(s, "-0") && !strings.HasPrefix(s, "-.0") }, + ), + rapid.StringMatching(fmt.Sprintf(`\d*\.\d{%d,}`, n+1)), + ).Draw(t, "s") + _, err := NewDecFromString(s, AssertNotNegative(), AssertMaxDecimals(n)) + require.Error(t, err) +} + +// Property: invalid_number_string(s) || IsNegative(s) || IsZero(s) +// => NewPositiveDecFromString(s) == err +func testInvalidNewPositiveDecFromString(t *rapid.T) { + s := rapid.OneOf( + rapid.StringMatching("[[:alpha:]]+"), + rapid.StringMatching(`^-\d*\.?\d+|0$`), + ).Draw(t, "s") + _, err := NewDecFromString(s, AssertGreaterThanZero()) + require.Error(t, err) +} + +// Property: invalid_number_string(s) || IsNegative(s) || IsZero(s) || NumDecimals(s) > n +// => NewPositiveFixedDecFromString(s) == err +func testInvalidNewPositiveFixedDecFromString(t *rapid.T) { + n := rapid.Uint32Range(0, 999).Draw(t, "n") + s := rapid.OneOf( + rapid.StringMatching("[[:alpha:]]+"), + rapid.StringMatching(`^-\d*\.?\d+|0$`), + rapid.StringMatching(fmt.Sprintf(`\d*\.\d{%d,}`, n+1)), + ).Draw(t, "s") + _, err := NewDecFromString(s, AssertGreaterThanZero(), AssertMaxDecimals(n)) + require.Error(t, err) +} + +// Property: 0 + a == a +func testAddLeftIdentity(t *rapid.T) { + a := genDec.Draw(t, "a") + zero := NewDecFromInt64(0) + + b, err := zero.Add(a) + require.NoError(t, err) + + require.True(t, a.Equal(b)) +} + +// Property: a + 0 == a +func testAddRightIdentity(t *rapid.T) { + a := genDec.Draw(t, "a") + zero := NewDecFromInt64(0) + + b, err := a.Add(zero) + require.NoError(t, err) + + require.True(t, a.Equal(b)) +} + +// Property: a + b == b + a +func testAddCommutative(t *rapid.T) { + a := genDec.Draw(t, "a") + b := genDec.Draw(t, "b") + + c, err := a.Add(b) + require.NoError(t, err) + + d, err := b.Add(a) + require.NoError(t, err) + + require.True(t, c.Equal(d)) +} + +// Property: (a + b) + c == a + (b + c) +func testAddAssociative(t *rapid.T) { + a := genDec.Draw(t, "a") + b := genDec.Draw(t, "b") + c := genDec.Draw(t, "c") + + // (a + b) + c + d, err := a.Add(b) + require.NoError(t, err) + + e, err := d.Add(c) + require.NoError(t, err) + + // a + (b + c) + f, err := b.Add(c) + require.NoError(t, err) + + g, err := a.Add(f) + require.NoError(t, err) + + require.True(t, e.Equal(g)) +} + +// Property: a - 0 == a +func testSubRightIdentity(t *rapid.T) { + a := genDec.Draw(t, "a") + zero := NewDecFromInt64(0) + + b, err := a.Sub(zero) + require.NoError(t, err) + + require.True(t, a.Equal(b)) +} + +// Property: a - a == 0 +func testSubZero(t *rapid.T) { + a := genDec.Draw(t, "a") + zero := NewDecFromInt64(0) + + b, err := a.Sub(a) + require.NoError(t, err) + + require.True(t, b.Equal(zero)) +} + +// Property: 1 * a == a +func testMulLeftIdentity(t *rapid.T) { + a := genDec.Draw(t, "a") + one := NewDecFromInt64(1) + + b, err := one.Mul(a) + require.NoError(t, err) + + require.True(t, a.Equal(b)) +} + +// Property: a * 1 == a +func testMulRightIdentity(t *rapid.T) { + a := genDec.Draw(t, "a") + one := NewDecFromInt64(1) + + b, err := a.Mul(one) + require.NoError(t, err) + + require.True(t, a.Equal(b)) +} + +// Property: a * b == b * a +func testMulCommutative(t *rapid.T) { + a := genDec.Draw(t, "a") + b := genDec.Draw(t, "b") + + c, err := a.Mul(b) + require.NoError(t, err) + + d, err := b.Mul(a) + require.NoError(t, err) + + require.True(t, c.Equal(d)) +} + +// Property: (a * b) * c == a * (b * c) +func testMulAssociative(t *rapid.T) { + a := genDec.Draw(t, "a") + b := genDec.Draw(t, "b") + c := genDec.Draw(t, "c") + + // (a * b) * c + d, err := a.Mul(b) + require.NoError(t, err) + + e, err := d.Mul(c) + require.NoError(t, err) + + // a * (b * c) + f, err := b.Mul(c) + require.NoError(t, err) + + g, err := a.Mul(f) + require.NoError(t, err) + + require.True(t, e.Equal(g)) +} + +// Property: (a - b) + b == a +func testSubAdd(t *rapid.T) { + a := genDec.Draw(t, "a") + b := genDec.Draw(t, "b") + + c, err := a.Sub(b) + require.NoError(t, err) + + d, err := c.Add(b) + require.NoError(t, err) + + require.True(t, a.Equal(d)) +} + +// Property: (a + b) - b == a +func testAddSub(t *rapid.T) { + a := genDec.Draw(t, "a") + b := genDec.Draw(t, "b") + + c, err := a.Add(b) + require.NoError(t, err) + + d, err := c.Sub(b) + require.NoError(t, err) + + require.True(t, a.Equal(d)) +} + +// Property: a * 0 = 0 +func testMulZero(t *rapid.T) { + a := genDec.Draw(t, "a") + zero := Dec{} + + c, err := a.Mul(zero) + require.NoError(t, err) + require.True(t, c.IsZero()) +} + +// Property: a/a = 1 +func testSelfQuo(t *rapid.T) { + decNotZero := func(d Dec) bool { return !d.IsZero() } + a := genDec.Filter(decNotZero).Draw(t, "a") + one := NewDecFromInt64(1) + + b, err := a.Quo(a) + require.NoError(t, err) + require.True(t, one.Equal(b)) +} + +// Property: a/1 = a +func testQuoByOne(t *rapid.T) { + a := genDec.Draw(t, "a") + one := NewDecFromInt64(1) + + b, err := a.Quo(one) + require.NoError(t, err) + require.True(t, a.Equal(b)) +} + +// Property: (a * b) / a == b +func testMulQuoA(t *rapid.T) { + decNotZero := func(d Dec) bool { return !d.IsZero() } + a := genDec.Filter(decNotZero).Draw(t, "a") + b := genDec.Draw(t, "b") + + c, err := a.Mul(b) + require.NoError(t, err) + + d, err := c.Quo(a) + require.NoError(t, err) + + require.True(t, b.Equal(d)) +} + +// Property: (a * b) / b == a +func testMulQuoB(t *rapid.T) { + decNotZero := func(d Dec) bool { return !d.IsZero() } + a := genDec.Draw(t, "a") + b := genDec.Filter(decNotZero).Draw(t, "b") + + c, err := a.Mul(b) + require.NoError(t, err) + + d, err := c.Quo(b) + require.NoError(t, err) + + require.True(t, a.Equal(d)) +} + +// Property: (a * 10^b) / 10^b == a using MulExact and QuoExact +// and a with no more than b decimal places (b <= 32). +func testMulQuoExact(t *rapid.T) { + b := rapid.Uint32Range(0, 32).Draw(t, "b") + decPrec := func(d Dec) bool { return d.NumDecimalPlaces() <= b } + a := genDec.Filter(decPrec).Draw(t, "a") + + c := NewDecWithPrec(1, int32(b)) + + d, err := a.MulExact(c) + require.NoError(t, err) + + e, err := d.QuoExact(c) + require.NoError(t, err) + + require.True(t, a.Equal(e)) +} + +// Property: (a / b) * b == a using QuoExact and MulExact and +// a as an integer. +func testQuoMulExact(t *rapid.T) { + a := rapid.Uint64().Draw(t, "a") + aDec, err := NewDecFromString(fmt.Sprintf("%d", a)) + require.NoError(t, err) + b := rapid.Uint32Range(0, 32).Draw(t, "b") + c := NewDecWithPrec(1, int32(b)) + + require.NoError(t, err) + + d, err := aDec.QuoExact(c) + require.NoError(t, err) + + e, err := d.MulExact(c) + require.NoError(t, err) + + require.True(t, aDec.Equal(e)) +} + +// Property: Cmp(a, b) == -Cmp(b, a) +func testCmpInverse(t *rapid.T) { + a := genDec.Draw(t, "a") + b := genDec.Draw(t, "b") + + require.Equal(t, a.Cmp(b), -b.Cmp(a)) +} + +// Property: Equal(a, b) == Equal(b, a) +func testEqualCommutative(t *rapid.T) { + a := genDec.Draw(t, "a") + b := genDec.Draw(t, "b") + + require.Equal(t, a.Equal(b), b.Equal(a)) +} + +// Property: isZero(f) == isZero(NewDecFromString(f.String())) +func testIsZero(t *rapid.T) { + floatAndDec := genFloatAndDec.Draw(t, "floatAndDec") + f, dec := floatAndDec.float, floatAndDec.dec + + require.Equal(t, f == 0, dec.IsZero()) +} + +// Property: isNegative(f) == isNegative(NewDecFromString(f.String())) +func testIsNegative(t *rapid.T) { + floatAndDec := genFloatAndDec.Draw(t, "floatAndDec") + f, dec := floatAndDec.float, floatAndDec.dec + + require.Equal(t, f < 0, dec.IsNegative()) +} + +// Property: isPositive(f) == isPositive(NewDecFromString(f.String())) +func testIsPositive(t *rapid.T) { + floatAndDec := genFloatAndDec.Draw(t, "floatAndDec") + f, dec := floatAndDec.float, floatAndDec.dec + + require.Equal(t, f > 0, dec.IsPositive()) +} + +// Property: floatDecimalPlaces(f) == NumDecimalPlaces(NewDecFromString(f.String())) +func testNumDecimalPlaces(t *rapid.T) { + floatAndDec := genFloatAndDec.Draw(t, "floatAndDec") + f, dec := floatAndDec.float, floatAndDec.dec + + require.Equal(t, floatDecimalPlaces(t, f), dec.NumDecimalPlaces()) +} + +func floatDecimalPlaces(t *rapid.T, f float64) uint32 { + reScientific := regexp.MustCompile(`^\-?(?:[[:digit:]]+(?:\.([[:digit:]]+))?|\.([[:digit:]]+))(?:e?(?:\+?([[:digit:]]+)|(-[[:digit:]]+)))?$`) + fStr := fmt.Sprintf("%g", f) + matches := reScientific.FindAllStringSubmatch(fStr, 1) + if len(matches) != 1 { + t.Fatalf("Didn't match float: %g", f) + } + + // basePlaces is the number of decimal places in the decimal part of the + // string + basePlaces := 0 + if matches[0][1] != "" { + basePlaces = len(matches[0][1]) + } else if matches[0][2] != "" { + basePlaces = len(matches[0][2]) + } + t.Logf("Base places: %d", basePlaces) + + // exp is the exponent + exp := 0 + if matches[0][3] != "" { + var err error + exp, err = strconv.Atoi(matches[0][3]) + require.NoError(t, err) + } else if matches[0][4] != "" { + var err error + exp, err = strconv.Atoi(matches[0][4]) + require.NoError(t, err) + } + + // Subtract exponent from base and check if negative + res := basePlaces - exp + if res <= 0 { + return 0 + } + + return uint32(res) +} diff --git a/math/dec_test.go b/math/dec_test.go index 1e72e173e84f..daa297a643c9 100644 --- a/math/dec_test.go +++ b/math/dec_test.go @@ -1,1028 +1,247 @@ -package math_test +package math import ( - "bytes" - "encoding/json" - "fmt" - "math/big" - "os" "strings" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" - "sigs.k8s.io/yaml" - - "cosmossdk.io/math" ) -type decimalTestSuite struct { - suite.Suite -} - -func TestDecimalTestSuite(t *testing.T) { - suite.Run(t, new(decimalTestSuite)) -} - -func TestDecApproxEq(t *testing.T) { - // d1 = 0.55, d2 = 0.6, tol = 0.1 - d1 := math.LegacyNewDecWithPrec(55, 2) - d2 := math.LegacyNewDecWithPrec(6, 1) - tol := math.LegacyNewDecWithPrec(1, 1) - - require.True(math.LegacyDecApproxEq(t, d1, d2, tol)) - - // d1 = 0.55, d2 = 0.6, tol = 1E-5 - d1 = math.LegacyNewDecWithPrec(55, 2) - d2 = math.LegacyNewDecWithPrec(6, 1) - tol = math.LegacyNewDecWithPrec(1, 5) - - require.False(math.LegacyDecApproxEq(t, d1, d2, tol)) - - // d1 = 0.6, d2 = 0.61, tol = 0.01 - d1 = math.LegacyNewDecWithPrec(6, 1) - d2 = math.LegacyNewDecWithPrec(61, 2) - tol = math.LegacyNewDecWithPrec(1, 2) - - require.True(math.LegacyDecApproxEq(t, d1, d2, tol)) -} - -// create a decimal from a decimal string (ex. "1234.5678") -func (s *decimalTestSuite) mustNewDecFromStr(str string) (d math.LegacyDec) { - d, err := math.LegacyNewDecFromStr(str) - s.Require().NoError(err) - - return d -} - -func (s *decimalTestSuite) TestNewDecFromStr() { - largeBigInt, ok := new(big.Int).SetString("3144605511029693144278234343371835", 10) - s.Require().True(ok) - - largerBigInt, ok := new(big.Int).SetString("8888888888888888888888888888888888888888888888888888888888888888888844444440", 10) - s.Require().True(ok) - - largestBigInt, ok := new(big.Int).SetString("33499189745056880149688856635597007162669032647290798121690100488888732861290034376435130433535", 10) - s.Require().True(ok) - - tests := []struct { - decimalStr string - expErr bool - exp math.LegacyDec +func TestNewDecFromString(t *testing.T) { + specs := map[string]struct { + src string + constraints []SetupConstraint + exp Dec + expErr error }{ - {"", true, math.LegacyDec{}}, - {"0.-75", true, math.LegacyDec{}}, - {"0", false, math.LegacyNewDec(0)}, - {"1", false, math.LegacyNewDec(1)}, - {"1.1", false, math.LegacyNewDecWithPrec(11, 1)}, - {"0.75", false, math.LegacyNewDecWithPrec(75, 2)}, - {"0.8", false, math.LegacyNewDecWithPrec(8, 1)}, - {"0.11111", false, math.LegacyNewDecWithPrec(11111, 5)}, - {"314460551102969.3144278234343371835", true, math.LegacyNewDec(3141203149163817869)}, - { - "314460551102969314427823434337.1835718092488231350", - true, math.LegacyNewDecFromBigIntWithPrec(largeBigInt, 4), + "simple decimal": { + src: "1", + exp: NewDecFromInt64(1), }, - { - "314460551102969314427823434337.1835", - false, math.LegacyNewDecFromBigIntWithPrec(largeBigInt, 4), + "simple negative decimal": { + src: "-1", + exp: NewDecFromInt64(-1), }, - {".", true, math.LegacyDec{}}, - {".0", true, math.LegacyNewDec(0)}, - {"1.", true, math.LegacyNewDec(1)}, - {"foobar", true, math.LegacyDec{}}, - {"0.foobar", true, math.LegacyDec{}}, - {"0.foobar.", true, math.LegacyDec{}}, - {"8888888888888888888888888888888888888888888888888888888888888888888844444440", false, math.LegacyNewDecFromBigInt(largerBigInt)}, - {"33499189745056880149688856635597007162669032647290798121690100488888732861290.034376435130433535", false, math.LegacyNewDecFromBigIntWithPrec(largestBigInt, 18)}, - {"133499189745056880149688856635597007162669032647290798121690100488888732861291", true, math.LegacyDec{}}, - } - - for tcIndex, tc := range tests { - res, err := math.LegacyNewDecFromStr(tc.decimalStr) - if tc.expErr { - s.Require().NotNil(err, "error expected, decimalStr %v, tc %v", tc.decimalStr, tcIndex) - } else { - s.Require().Nil(err, "unexpected error, decimalStr %v, tc %v", tc.decimalStr, tcIndex) - s.Require().True(res.Equal(tc.exp), "equality was incorrect, res %v, expTruncated %v, tc %v", res, tc.exp, tcIndex) - } - - // negative tc - res, err = math.LegacyNewDecFromStr("-" + tc.decimalStr) - if tc.expErr { - s.Require().NotNil(err, "error expected, decimalStr %v, tc %v", tc.decimalStr, tcIndex) - } else { - s.Require().Nil(err, "unexpected error, decimalStr %v, tc %v", tc.decimalStr, tcIndex) - exp := tc.exp.Mul(math.LegacyNewDec(-1)) - s.Require().True(res.Equal(exp), "equality was incorrect, res %v, expTruncated %v, tc %v", res, exp, tcIndex) - } - } -} - -func (s *decimalTestSuite) TestDecString() { - tests := []struct { - d math.LegacyDec - want string - }{ - {math.LegacyNewDec(0), "0.000000000000000000"}, - {math.LegacyNewDec(1), "1.000000000000000000"}, - {math.LegacyNewDec(10), "10.000000000000000000"}, - {math.LegacyNewDec(12340), "12340.000000000000000000"}, - {math.LegacyNewDecWithPrec(12340, 4), "1.234000000000000000"}, - {math.LegacyNewDecWithPrec(12340, 5), "0.123400000000000000"}, - {math.LegacyNewDecWithPrec(12340, 8), "0.000123400000000000"}, - {math.LegacyNewDecWithPrec(1009009009009009009, 17), "10.090090090090090090"}, - } - for tcIndex, tc := range tests { - s.Require().Equal(tc.want, tc.d.String(), "bad String(), index: %v", tcIndex) - } -} - -func (s *decimalTestSuite) TestDecFloat64() { - tests := []struct { - d math.LegacyDec - want float64 - }{ - {math.LegacyNewDec(0), 0.000000000000000000}, - {math.LegacyNewDec(1), 1.000000000000000000}, - {math.LegacyNewDec(10), 10.000000000000000000}, - {math.LegacyNewDec(12340), 12340.000000000000000000}, - {math.LegacyNewDecWithPrec(12340, 4), 1.234000000000000000}, - {math.LegacyNewDecWithPrec(12340, 5), 0.123400000000000000}, - {math.LegacyNewDecWithPrec(12340, 8), 0.000123400000000000}, - {math.LegacyNewDecWithPrec(1009009009009009009, 17), 10.090090090090090090}, - } - for tcIndex, tc := range tests { - value, err := tc.d.Float64() - s.Require().Nil(err, "error getting Float64(), index: %v", tcIndex) - s.Require().Equal(tc.want, value, "bad Float64(), index: %v", tcIndex) - s.Require().Equal(tc.want, tc.d.MustFloat64(), "bad MustFloat64(), index: %v", tcIndex) - } -} - -func (s *decimalTestSuite) TestEqualities() { - tests := []struct { - d1, d2 math.LegacyDec - gt, lt, eq bool - }{ - {math.LegacyNewDec(0), math.LegacyNewDec(0), false, false, true}, - {math.LegacyNewDecWithPrec(0, 2), math.LegacyNewDecWithPrec(0, 4), false, false, true}, - {math.LegacyNewDecWithPrec(100, 0), math.LegacyNewDecWithPrec(100, 0), false, false, true}, - {math.LegacyNewDecWithPrec(-100, 0), math.LegacyNewDecWithPrec(-100, 0), false, false, true}, - {math.LegacyNewDecWithPrec(-1, 1), math.LegacyNewDecWithPrec(-1, 1), false, false, true}, - {math.LegacyNewDecWithPrec(3333, 3), math.LegacyNewDecWithPrec(3333, 3), false, false, true}, - - {math.LegacyNewDecWithPrec(0, 0), math.LegacyNewDecWithPrec(3333, 3), false, true, false}, - {math.LegacyNewDecWithPrec(0, 0), math.LegacyNewDecWithPrec(100, 0), false, true, false}, - {math.LegacyNewDecWithPrec(-1, 0), math.LegacyNewDecWithPrec(3333, 3), false, true, false}, - {math.LegacyNewDecWithPrec(-1, 0), math.LegacyNewDecWithPrec(100, 0), false, true, false}, - {math.LegacyNewDecWithPrec(1111, 3), math.LegacyNewDecWithPrec(100, 0), false, true, false}, - {math.LegacyNewDecWithPrec(1111, 3), math.LegacyNewDecWithPrec(3333, 3), false, true, false}, - {math.LegacyNewDecWithPrec(-3333, 3), math.LegacyNewDecWithPrec(-1111, 3), false, true, false}, - - {math.LegacyNewDecWithPrec(3333, 3), math.LegacyNewDecWithPrec(0, 0), true, false, false}, - {math.LegacyNewDecWithPrec(100, 0), math.LegacyNewDecWithPrec(0, 0), true, false, false}, - {math.LegacyNewDecWithPrec(3333, 3), math.LegacyNewDecWithPrec(-1, 0), true, false, false}, - {math.LegacyNewDecWithPrec(100, 0), math.LegacyNewDecWithPrec(-1, 0), true, false, false}, - {math.LegacyNewDecWithPrec(100, 0), math.LegacyNewDecWithPrec(1111, 3), true, false, false}, - {math.LegacyNewDecWithPrec(3333, 3), math.LegacyNewDecWithPrec(1111, 3), true, false, false}, - {math.LegacyNewDecWithPrec(-1111, 3), math.LegacyNewDecWithPrec(-3333, 3), true, false, false}, - } - - for tcIndex, tc := range tests { - s.Require().Equal(tc.gt, tc.d1.GT(tc.d2), "GT result is incorrect, tc %d", tcIndex) - s.Require().Equal(tc.lt, tc.d1.LT(tc.d2), "LT result is incorrect, tc %d", tcIndex) - s.Require().Equal(tc.eq, tc.d1.Equal(tc.d2), "equality result is incorrect, tc %d", tcIndex) - } -} - -func (s *decimalTestSuite) TestDecsEqual() { - tests := []struct { - d1s, d2s []math.LegacyDec - eq bool - }{ - {[]math.LegacyDec{math.LegacyNewDec(0)}, []math.LegacyDec{math.LegacyNewDec(0)}, true}, - {[]math.LegacyDec{math.LegacyNewDec(0)}, []math.LegacyDec{math.LegacyNewDec(1)}, false}, - {[]math.LegacyDec{math.LegacyNewDec(0)}, []math.LegacyDec{}, false}, - {[]math.LegacyDec{math.LegacyNewDec(0), math.LegacyNewDec(1)}, []math.LegacyDec{math.LegacyNewDec(0), math.LegacyNewDec(1)}, true}, - {[]math.LegacyDec{math.LegacyNewDec(1), math.LegacyNewDec(0)}, []math.LegacyDec{math.LegacyNewDec(1), math.LegacyNewDec(0)}, true}, - {[]math.LegacyDec{math.LegacyNewDec(1), math.LegacyNewDec(0)}, []math.LegacyDec{math.LegacyNewDec(0), math.LegacyNewDec(1)}, false}, - {[]math.LegacyDec{math.LegacyNewDec(1), math.LegacyNewDec(0)}, []math.LegacyDec{math.LegacyNewDec(1)}, false}, - {[]math.LegacyDec{math.LegacyNewDec(1), math.LegacyNewDec(2)}, []math.LegacyDec{math.LegacyNewDec(2), math.LegacyNewDec(4)}, false}, - {[]math.LegacyDec{math.LegacyNewDec(3), math.LegacyNewDec(18)}, []math.LegacyDec{math.LegacyNewDec(1), math.LegacyNewDec(6)}, false}, - } - - for tcIndex, tc := range tests { - s.Require().Equal(tc.eq, math.LegacyDecsEqual(tc.d1s, tc.d2s), "equality of decional arrays is incorrect, tc %d", tcIndex) - s.Require().Equal(tc.eq, math.LegacyDecsEqual(tc.d2s, tc.d1s), "equality of decional arrays is incorrect (converse), tc %d", tcIndex) - } -} - -func (s *decimalTestSuite) TestArithmetic() { - tests := []struct { - d1, d2 math.LegacyDec - expMul, expMulTruncate, expMulRoundUp math.LegacyDec - expQuo, expQuoRoundUp, expQuoTruncate math.LegacyDec - expAdd, expSub math.LegacyDec - }{ - // d1 d2 MUL MulTruncate MulRoundUp QUO QUORoundUp QUOTrunctate ADD SUB - {math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0)}, - {math.LegacyNewDec(1), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(1), math.LegacyNewDec(1)}, - {math.LegacyNewDec(0), math.LegacyNewDec(1), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(1), math.LegacyNewDec(-1)}, - {math.LegacyNewDec(0), math.LegacyNewDec(-1), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(-1), math.LegacyNewDec(1)}, - {math.LegacyNewDec(-1), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(0), math.LegacyNewDec(-1), math.LegacyNewDec(-1)}, - - {math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(2), math.LegacyNewDec(0)}, - {math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(-2), math.LegacyNewDec(0)}, - {math.LegacyNewDec(1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(0), math.LegacyNewDec(2)}, - {math.LegacyNewDec(-1), math.LegacyNewDec(1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(-1), math.LegacyNewDec(0), math.LegacyNewDec(-2)}, - - { - math.LegacyNewDec(3), math.LegacyNewDec(7), math.LegacyNewDec(21), math.LegacyNewDec(21), math.LegacyNewDec(21), - math.LegacyNewDecWithPrec(428571428571428571, 18), math.LegacyNewDecWithPrec(428571428571428572, 18), math.LegacyNewDecWithPrec(428571428571428571, 18), - math.LegacyNewDec(10), math.LegacyNewDec(-4), + "valid decimal with decimal places": { + src: "1.234", + exp: NewDecWithPrec(1234, -3), }, - { - math.LegacyNewDec(2), math.LegacyNewDec(4), math.LegacyNewDec(8), math.LegacyNewDec(8), math.LegacyNewDec(8), math.LegacyNewDecWithPrec(5, 1), math.LegacyNewDecWithPrec(5, 1), math.LegacyNewDecWithPrec(5, 1), - math.LegacyNewDec(6), math.LegacyNewDec(-2), + "valid negative decimal": { + src: "-1.234", + exp: NewDecWithPrec(-1234, -3), }, - - {math.LegacyNewDec(100), math.LegacyNewDec(100), math.LegacyNewDec(10000), math.LegacyNewDec(10000), math.LegacyNewDec(10000), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(200), math.LegacyNewDec(0)}, - - { - math.LegacyNewDecWithPrec(15, 1), math.LegacyNewDecWithPrec(15, 1), math.LegacyNewDecWithPrec(225, 2), math.LegacyNewDecWithPrec(225, 2), math.LegacyNewDecWithPrec(225, 2), - math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(3), math.LegacyNewDec(0), + "min decimal": { + src: "-" + strings.Repeat("9", 34), + exp: must(NewDecWithPrec(-1, 34).Add(NewDecFromInt64(1))), }, - { - math.LegacyNewDecWithPrec(3333, 4), math.LegacyNewDecWithPrec(333, 4), math.LegacyNewDecWithPrec(1109889, 8), math.LegacyNewDecWithPrec(1109889, 8), math.LegacyNewDecWithPrec(1109889, 8), - math.LegacyMustNewDecFromStr("10.009009009009009009"), math.LegacyMustNewDecFromStr("10.009009009009009010"), math.LegacyMustNewDecFromStr("10.009009009009009009"), - math.LegacyNewDecWithPrec(3666, 4), math.LegacyNewDecWithPrec(3, 1), + "max decimal": { + // todo: src: strings.Repeat("9", 34), + exp: must(NewDecWithPrec(1, 34).Sub(NewDecFromInt64(1))), }, - } - - for tcIndex, tc := range tests { - tc := tc - resAdd := tc.d1.Add(tc.d2) - resSub := tc.d1.Sub(tc.d2) - resMul := tc.d1.Mul(tc.d2) - resMulTruncate := tc.d1.MulTruncate(tc.d2) - resMulRoundUp := tc.d1.MulRoundUp(tc.d2) - s.Require().True(tc.expAdd.Equal(resAdd), "expTruncated %v, res %v, tc %d", tc.expAdd, resAdd, tcIndex) - s.Require().True(tc.expSub.Equal(resSub), "expTruncated %v, res %v, tc %d", tc.expSub, resSub, tcIndex) - s.Require().True(tc.expMul.Equal(resMul), "expTruncated %v, res %v, tc %d", tc.expMul, resMul, tcIndex) - s.Require().True(tc.expMulTruncate.Equal(resMulTruncate), "expTruncated %v, res %v, tc %d", tc.expMulTruncate, resMulTruncate, tcIndex) - s.Require().True(tc.expMulRoundUp.Equal(resMulRoundUp), "expTruncated %v, res %v, tc %d", tc.expMulRoundUp, resMulRoundUp, tcIndex) - - if tc.d2.IsZero() { // panic for divide by zero - s.Require().Panics(func() { tc.d1.Quo(tc.d2) }) - } else { - resQuo := tc.d1.Quo(tc.d2) - s.Require().True(tc.expQuo.Equal(resQuo), "expTruncated %v, res %v, tc %d", tc.expQuo.String(), resQuo.String(), tcIndex) - - resQuoRoundUp := tc.d1.QuoRoundUp(tc.d2) - s.Require().True(tc.expQuoRoundUp.Equal(resQuoRoundUp), "expTruncated %v, res %v, tc %d", - tc.expQuoRoundUp.String(), resQuoRoundUp.String(), tcIndex) - - resQuoTruncate := tc.d1.QuoTruncate(tc.d2) - s.Require().True(tc.expQuoTruncate.Equal(resQuoTruncate), "expTruncated %v, res %v, tc %d", - tc.expQuoTruncate.String(), resQuoTruncate.String(), tcIndex) - } - } -} - -func (s *decimalTestSuite) TestMulRoundUp_RoundingAtPrecisionEnd() { - var ( - a = math.LegacyMustNewDecFromStr("0.000000000000000009") - b = math.LegacyMustNewDecFromStr("0.000000000000000009") - expectedRoundUp = math.LegacyMustNewDecFromStr("0.000000000000000001") - expectedTruncate = math.LegacyMustNewDecFromStr("0.000000000000000000") - ) - - actualRoundUp := a.MulRoundUp(b) - s.Require().Equal(expectedRoundUp.String(), actualRoundUp.String(), "expTruncated %v, res %v", expectedRoundUp, actualRoundUp) - - actualTruncate := a.MulTruncate(b) - s.Require().Equal(expectedTruncate.String(), actualTruncate.String(), "expTruncated %v, res %v", expectedRoundUp, actualTruncate) -} - -func (s *decimalTestSuite) TestBankerRoundChop() { - tests := []struct { - d1 math.LegacyDec - exp int64 - }{ - {s.mustNewDecFromStr("0.25"), 0}, - {s.mustNewDecFromStr("0"), 0}, - {s.mustNewDecFromStr("1"), 1}, - {s.mustNewDecFromStr("0.75"), 1}, - {s.mustNewDecFromStr("0.5"), 0}, - {s.mustNewDecFromStr("7.5"), 8}, - {s.mustNewDecFromStr("1.5"), 2}, - {s.mustNewDecFromStr("2.5"), 2}, - {s.mustNewDecFromStr("0.545"), 1}, // 0.545-> 1 even though 5 is first decimal and 1 not even - {s.mustNewDecFromStr("1.545"), 2}, - } - - for tcIndex, tc := range tests { - resNeg := tc.d1.Neg().RoundInt64() - s.Require().Equal(-1*tc.exp, resNeg, "negative tc %d", tcIndex) - - resPos := tc.d1.RoundInt64() - s.Require().Equal(tc.exp, resPos, "positive tc %d", tcIndex) - } -} - -func (s *decimalTestSuite) TestTruncate() { - tests := []struct { - d1 math.LegacyDec - exp int64 - }{ - {s.mustNewDecFromStr("0"), 0}, - {s.mustNewDecFromStr("0.25"), 0}, - {s.mustNewDecFromStr("0.75"), 0}, - {s.mustNewDecFromStr("1"), 1}, - {s.mustNewDecFromStr("1.5"), 1}, - {s.mustNewDecFromStr("7.5"), 7}, - {s.mustNewDecFromStr("7.6"), 7}, - {s.mustNewDecFromStr("7.4"), 7}, - {s.mustNewDecFromStr("100.1"), 100}, - {s.mustNewDecFromStr("1000.1"), 1000}, - } - - for tcIndex, tc := range tests { - resNeg := tc.d1.Neg().TruncateInt64() - s.Require().Equal(-1*tc.exp, resNeg, "negative tc %d", tcIndex) - - resPos := tc.d1.TruncateInt64() - s.Require().Equal(tc.exp, resPos, "positive tc %d", tcIndex) - } -} - -func (s *decimalTestSuite) TestStringOverflow() { - // two random 64 bit primes - dec1, err := math.LegacyNewDecFromStr("51643150036226787134389711697696177267") - s.Require().NoError(err) - dec2, err := math.LegacyNewDecFromStr("-31798496660535729618459429845579852627") - s.Require().NoError(err) - dec3 := dec1.Add(dec2) - s.Require().Equal( - "19844653375691057515930281852116324640.000000000000000000", - dec3.String(), - ) -} - -func (s *decimalTestSuite) TestDecMulInt() { - tests := []struct { - sdkDec math.LegacyDec - sdkInt math.Int - want math.LegacyDec - }{ - {math.LegacyNewDec(10), math.NewInt(2), math.LegacyNewDec(20)}, - {math.LegacyNewDec(1000000), math.NewInt(100), math.LegacyNewDec(100000000)}, - {math.LegacyNewDecWithPrec(1, 1), math.NewInt(10), math.LegacyNewDec(1)}, - {math.LegacyNewDecWithPrec(1, 5), math.NewInt(20), math.LegacyNewDecWithPrec(2, 4)}, - } - for i, tc := range tests { - got := tc.sdkDec.MulInt(tc.sdkInt) - s.Require().Equal(tc.want, got, "Incorrect result on test case %d", i) - } -} - -func (s *decimalTestSuite) TestDecCeil() { - testCases := []struct { - input math.LegacyDec - expected math.LegacyDec - }{ - {math.LegacyNewDecWithPrec(1000000000000000, math.LegacyPrecision), math.LegacyNewDec(1)}, // 0.001 => 1.0 - {math.LegacyNewDecWithPrec(-1000000000000000, math.LegacyPrecision), math.LegacyZeroDec()}, // -0.001 => 0.0 - {math.LegacyZeroDec(), math.LegacyZeroDec()}, // 0.0 => 0.0 - {math.LegacyNewDecWithPrec(900000000000000000, math.LegacyPrecision), math.LegacyNewDec(1)}, // 0.9 => 1.0 - {math.LegacyNewDecWithPrec(4001000000000000000, math.LegacyPrecision), math.LegacyNewDec(5)}, // 4.001 => 5.0 - {math.LegacyNewDecWithPrec(-4001000000000000000, math.LegacyPrecision), math.LegacyNewDec(-4)}, // -4.001 => -4.0 - {math.LegacyNewDecWithPrec(4700000000000000000, math.LegacyPrecision), math.LegacyNewDec(5)}, // 4.7 => 5.0 - {math.LegacyNewDecWithPrec(-4700000000000000000, math.LegacyPrecision), math.LegacyNewDec(-4)}, // -4.7 => -4.0 - } - - for i, tc := range testCases { - res := tc.input.Ceil() - s.Require().Equal(tc.expected, res, "unexpected result for test case %d, input: %v", i, tc.input) - } -} - -func (s *decimalTestSuite) TestCeilOverflow() { - d, err := math.LegacyNewDecFromStr("66749594872528440074844428317798503581334516323645399060845050244444366430645.000000000000000001") - s.Require().NoError(err) - s.Require().True(d.BigInt().BitLen() <= 315, "d is too large") - // this call panics because the value is too large - s.Require().Panics(func() { d.Ceil() }, "Ceil should panic on overflow") -} - -func (s *decimalTestSuite) TestPower() { - testCases := []struct { - input math.LegacyDec - power uint64 - expected math.LegacyDec - }{ - {math.LegacyNewDec(100), 0, math.LegacyOneDec()}, // 10 ^ (0) => 1.0 - {math.LegacyOneDec(), 10, math.LegacyOneDec()}, // 1.0 ^ (10) => 1.0 - {math.LegacyNewDecWithPrec(5, 1), 2, math.LegacyNewDecWithPrec(25, 2)}, // 0.5 ^ 2 => 0.25 - {math.LegacyNewDecWithPrec(2, 1), 2, math.LegacyNewDecWithPrec(4, 2)}, // 0.2 ^ 2 => 0.04 - {math.LegacyNewDecFromInt(math.NewInt(3)), 3, math.LegacyNewDecFromInt(math.NewInt(27))}, // 3 ^ 3 => 27 - {math.LegacyNewDecFromInt(math.NewInt(-3)), 4, math.LegacyNewDecFromInt(math.NewInt(81))}, // -3 ^ 4 = 81 - {math.LegacyNewDecWithPrec(1414213562373095049, 18), 2, math.LegacyNewDecFromInt(math.NewInt(2))}, // 1.414213562373095049 ^ 2 = 2 - } - - for i, tc := range testCases { - res := tc.input.Power(tc.power) - s.Require().True(tc.expected.Sub(res).Abs().LTE(math.LegacySmallestDec()), "unexpected result for test case %d, normal power, input: %v", i, tc.input) - - mutableInput := tc.input - mutableInput.PowerMut(tc.power) - s.Require().True(tc.expected.Sub(mutableInput).Abs().LTE(math.LegacySmallestDec()), - "unexpected result for test case %d, input %v", i, tc.input) - s.Require().True(res.Equal(tc.input), "unexpected result for test case %d, mutable power, input: %v", i, tc.input) - } -} - -func (s *decimalTestSuite) TestApproxRoot() { - testCases := []struct { - input math.LegacyDec - root uint64 - expected math.LegacyDec - }{ - {math.LegacyOneDec(), 10, math.LegacyOneDec()}, // 1.0 ^ (0.1) => 1.0 - {math.LegacyNewDecWithPrec(25, 2), 2, math.LegacyNewDecWithPrec(5, 1)}, // 0.25 ^ (0.5) => 0.5 - {math.LegacyNewDecWithPrec(4, 2), 2, math.LegacyNewDecWithPrec(2, 1)}, // 0.04 ^ (0.5) => 0.2 - {math.LegacyNewDecFromInt(math.NewInt(27)), 3, math.LegacyNewDecFromInt(math.NewInt(3))}, // 27 ^ (1/3) => 3 - {math.LegacyNewDecFromInt(math.NewInt(-81)), 4, math.LegacyNewDecFromInt(math.NewInt(-3))}, // -81 ^ (0.25) => -3 - {math.LegacyNewDecFromInt(math.NewInt(2)), 2, math.LegacyNewDecWithPrec(1414213562373095049, 18)}, // 2 ^ (0.5) => 1.414213562373095049 - {math.LegacyNewDecWithPrec(1005, 3), 31536000, math.LegacyMustNewDecFromStr("1.000000000158153904")}, // 1.005 ^ (1/31536000) ≈ 1.00000000016 - {math.LegacySmallestDec(), 2, math.LegacyNewDecWithPrec(1, 9)}, // 1e-18 ^ (0.5) => 1e-9 - {math.LegacySmallestDec(), 3, math.LegacyMustNewDecFromStr("0.000000999999999997")}, // 1e-18 ^ (1/3) => 1e-6 - {math.LegacyNewDecWithPrec(1, 8), 3, math.LegacyMustNewDecFromStr("0.002154434690031900")}, // 1e-8 ^ (1/3) ≈ 0.00215443469 - {math.LegacyMustNewDecFromStr("9000002314687921634000000000000000000021394871242000000000000000"), 2, math.LegacyMustNewDecFromStr("94868342004527103646332858502867.899477053226766107")}, - } - - // In the case of 1e-8 ^ (1/3), the result repeats every 5 iterations starting from iteration 24 - // (i.e. 24, 29, 34, ... give the same result) and never converges enough. The maximum number of - // iterations (300) causes the result at iteration 300 to be returned, regardless of convergence. - - for i, tc := range testCases { - res, err := tc.input.ApproxRoot(tc.root) - s.Require().NoError(err) - s.Require().True(tc.expected.Sub(res).Abs().LTE(math.LegacySmallestDec()), "unexpected result for test case %d, input: %v", i, tc.input) - } -} - -func (s *decimalTestSuite) TestApproxSqrt() { - testCases := []struct { - input math.LegacyDec - expected math.LegacyDec - }{ - {math.LegacyOneDec(), math.LegacyOneDec()}, // 1.0 => 1.0 - {math.LegacyNewDecWithPrec(25, 2), math.LegacyNewDecWithPrec(5, 1)}, // 0.25 => 0.5 - {math.LegacyNewDecWithPrec(4, 2), math.LegacyNewDecWithPrec(2, 1)}, // 0.09 => 0.3 - {math.LegacyNewDec(9), math.LegacyNewDecFromInt(math.NewInt(3))}, // 9 => 3 - {math.LegacyNewDec(-9), math.LegacyNewDecFromInt(math.NewInt(-3))}, // -9 => -3 - {math.LegacyNewDec(2), math.LegacyNewDecWithPrec(1414213562373095049, 18)}, // 2 => 1.414213562373095049 - { // 2^127 - 1 => 13043817825332782212.3495718062525083688 which rounds to 13043817825332782212.3495718062525083689 - math.LegacyNewDec(2).Power(127).Sub(math.LegacyOneDec()), - math.LegacyMustNewDecFromStr("13043817825332782212.349571806252508369"), + "precision too high": { + src: "." + strings.Repeat("9", 35), + expErr: ErrInvalidDecString, + }, + "decimal too big": { + // todo: src: strings.Repeat("9", 35), // 10^100000+10 + expErr: ErrInvalidDecString, + }, + "decimal too small": { + src: strings.Repeat("9", 35), // -10^100000+0.99999999999999999... +1 + expErr: ErrInvalidDecString, + }, + "valid decimal with leading zero": { + src: "01234", + exp: NewDecWithPrec(1234, 0), + }, + "valid decimal without leading zero": { + src: ".1234", + exp: NewDecWithPrec(1234, -4), }, - {math.LegacyMustNewDecFromStr("1.000000011823380862"), math.LegacyMustNewDecFromStr("1.000000005911690414")}, - } - - for i, tc := range testCases { - res, err := tc.input.ApproxSqrt() - s.Require().NoError(err) - s.Require().Equal(tc.expected, res, "unexpected result for test case %d, input: %v", i, tc.input) - } -} - -func (s *decimalTestSuite) TestDecSortableBytes() { - tests := []struct { - d math.LegacyDec - want []byte - }{ - {math.LegacyNewDec(0), []byte("000000000000000000.000000000000000000")}, - {math.LegacyNewDec(1), []byte("000000000000000001.000000000000000000")}, - {math.LegacyNewDec(10), []byte("000000000000000010.000000000000000000")}, - {math.LegacyNewDec(12340), []byte("000000000000012340.000000000000000000")}, - {math.LegacyNewDecWithPrec(12340, 4), []byte("000000000000000001.234000000000000000")}, - {math.LegacyNewDecWithPrec(12340, 5), []byte("000000000000000000.123400000000000000")}, - {math.LegacyNewDecWithPrec(12340, 8), []byte("000000000000000000.000123400000000000")}, - {math.LegacyNewDecWithPrec(1009009009009009009, 17), []byte("000000000000000010.090090090090090090")}, - {math.LegacyNewDecWithPrec(-1009009009009009009, 17), []byte("-000000000000000010.090090090090090090")}, - {math.LegacyNewDec(1000000000000000000), []byte("max")}, - {math.LegacyNewDec(-1000000000000000000), []byte("--")}, - } - for tcIndex, tc := range tests { - s.Require().Equal(tc.want, math.LegacySortableDecBytes(tc.d), "bad String(), index: %v", tcIndex) - } - - s.Require().Panics(func() { math.LegacySortableDecBytes(math.LegacyNewDec(1000000000000000001)) }) - s.Require().Panics(func() { math.LegacySortableDecBytes(math.LegacyNewDec(-1000000000000000001)) }) -} - -func (s *decimalTestSuite) TestDecEncoding() { - largestBigInt, ok := new(big.Int).SetString("33499189745056880149688856635597007162669032647290798121690100488888732861290034376435130433535", 10) - s.Require().True(ok) - smallestBigInt, ok := new(big.Int).SetString("-33499189745056880149688856635597007162669032647290798121690100488888732861290034376435130433535", 10) - s.Require().True(ok) + "valid decimal without trailing digits": { + src: "123.", + exp: NewDecWithPrec(123, 0), + }, - const maxDecBitLen = 315 - maxInt, ok := new(big.Int).SetString(strings.Repeat("1", maxDecBitLen), 2) - s.Require().True(ok) + "valid negative decimal without leading zero": { + src: "-.1234", + exp: NewDecWithPrec(-1234, -4), + }, - testCases := []struct { - input math.LegacyDec - rawBz string - jsonStr string - yamlStr string - }{ - { - math.LegacyNewDec(0), "30", - "\"0.000000000000000000\"", - "\"0.000000000000000000\"\n", + "valid negative decimal without trailing digits": { + src: "-123.", + exp: NewDecWithPrec(-123, 0), }, - { - math.LegacyNewDecWithPrec(4, 2), - "3430303030303030303030303030303030", - "\"0.040000000000000000\"", - "\"0.040000000000000000\"\n", + + "decimal with scientific notation": { + src: "1.23e4", + exp: NewDecWithPrec(123, 2), }, - { - math.LegacyNewDecWithPrec(-4, 2), - "2D3430303030303030303030303030303030", - "\"-0.040000000000000000\"", - "\"-0.040000000000000000\"\n", + "negative decimal with scientific notation": { + src: "-1.23e4", + exp: NewDecWithPrec(-123, 2), }, - { - math.LegacyNewDecWithPrec(1414213562373095049, 18), - "31343134323133353632333733303935303439", - "\"1.414213562373095049\"", - "\"1.414213562373095049\"\n", + "with setup constraint": { + src: "-1", + constraints: []SetupConstraint{AssertNotNegative()}, + expErr: ErrInvalidDecString, }, - { - math.LegacyNewDecWithPrec(-1414213562373095049, 18), - "2D31343134323133353632333733303935303439", - "\"-1.414213562373095049\"", - "\"-1.414213562373095049\"\n", + "empty string": { + src: "", + expErr: ErrInvalidDecString, }, - { - math.LegacyNewDecFromBigIntWithPrec(largestBigInt, 18), - "3333343939313839373435303536383830313439363838383536363335353937303037313632363639303332363437323930373938313231363930313030343838383838373332383631323930303334333736343335313330343333353335", - "\"33499189745056880149688856635597007162669032647290798121690100488888732861290.034376435130433535\"", - "\"33499189745056880149688856635597007162669032647290798121690100488888732861290.034376435130433535\"\n", + "NaN": { + src: "NaN", + expErr: ErrInvalidDecString, }, - { - math.LegacyNewDecFromBigIntWithPrec(smallestBigInt, 18), - "2D3333343939313839373435303536383830313439363838383536363335353937303037313632363639303332363437323930373938313231363930313030343838383838373332383631323930303334333736343335313330343333353335", - "\"-33499189745056880149688856635597007162669032647290798121690100488888732861290.034376435130433535\"", - "\"-33499189745056880149688856635597007162669032647290798121690100488888732861290.034376435130433535\"\n", + "random string": { + src: "1foo", + expErr: ErrInvalidDecString, }, - { - math.LegacyNewDecFromBigIntWithPrec(maxInt, 18), - "3636373439353934383732353238343430303734383434343238333137373938353033353831333334353136333233363435333939303630383435303530323434343434333636343330363435303137313838323137353635323136373637", - "\"66749594872528440074844428317798503581334516323645399060845050244444366430645.017188217565216767\"", - "\"66749594872528440074844428317798503581334516323645399060845050244444366430645.017188217565216767\"\n", + "Infinity": { + src: "Infinity", + expErr: ErrInfiniteString, }, - } - - for _, tc := range testCases { - bz, err := tc.input.Marshal() - s.Require().NoError(err) - s.Require().Equal(tc.rawBz, fmt.Sprintf("%X", bz)) - - var other math.LegacyDec - s.Require().NoError((&other).Unmarshal(bz)) - s.Require().True(tc.input.Equal(other)) - - bz, err = json.Marshal(tc.input) - s.Require().NoError(err) - s.Require().Equal(tc.jsonStr, string(bz)) - s.Require().NoError(json.Unmarshal(bz, &other)) - s.Require().True(tc.input.Equal(other)) - - bz, err = yaml.Marshal(tc.input) - s.Require().NoError(err) - s.Require().Equal(tc.yamlStr, string(bz)) - } -} - -// Showcase that different orders of operations causes different results. -func (s *decimalTestSuite) TestOperationOrders() { - n1 := math.LegacyNewDec(10) - n2 := math.LegacyNewDec(1000000010) - s.Require().Equal(n1.Mul(n2).Quo(n2), math.LegacyNewDec(10)) - s.Require().NotEqual(n1.Mul(n2).Quo(n2), n1.Quo(n2).Mul(n2)) -} - -func BenchmarkMarshalTo(b *testing.B) { - b.ReportAllocs() - bis := []struct { - in math.LegacyDec - want []byte - }{ - { - math.LegacyNewDec(1e8), []byte{ - 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, - 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, - 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, - }, + "Inf": { + src: "Inf", + expErr: ErrInfiniteString, }, - {math.LegacyNewDec(0), []byte{0x30}}, } - data := make([]byte, 100) - - b.ReportAllocs() - b.ResetTimer() - - for i := 0; i < b.N; i++ { - for _, bi := range bis { - if n, err := bi.in.MarshalTo(data); err != nil { - b.Fatal(err) - } else if !bytes.Equal(data[:n], bi.want) { - b.Fatalf("Mismatch\nGot: % x\nWant: % x\n", data[:n], bi.want) + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + got, gotErr := NewDecFromString(spec.src, spec.constraints...) + if spec.expErr != nil { + require.ErrorIs(t, gotErr, spec.expErr, got.String()) + return } - } + require.NoError(t, gotErr) + assert.Equal(t, spec.exp.String(), got.String()) + }) } } -var sink interface{} - -func BenchmarkLegacyQuoMut(b *testing.B) { - b1 := math.LegacyNewDec(17e2 + 8371) - b2 := math.LegacyNewDec(4371) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - sink = b1.QuoMut(b2) - } - - if sink == nil { - b.Fatal("Benchmark did not run") - } - sink = (interface{})(nil) +func TestIsFinite(t *testing.T) { + a, err := NewDecFromString("1.5") + require.NoError(t, err) + require.True(t, a.IsFinite()) } -func BenchmarkLegacyQuoTruncateMut(b *testing.B) { - b1 := math.LegacyNewDec(17e2 + 8371) - baseArr := make([]math.LegacyDec, b.N) - for i := 0; i < b.N; i++ { - baseArr[i] = b1.Clone() - } - b2 := math.LegacyNewDec(4371) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - sink = baseArr[i].QuoTruncateMut(b2) - } - - if sink == nil { - b.Fatal("Benchmark did not run") - } - sink = (interface{})(nil) +func TestReduce(t *testing.T) { + a, err := NewDecFromString("1.30000") + require.NoError(t, err) + b, n := a.Reduce() + require.Equal(t, 4, n) + require.True(t, a.Equal(b)) + require.Equal(t, "1.3", b.String()) } -func BenchmarkLegacySqrtOnMersennePrime(b *testing.B) { - b1 := math.LegacyNewDec(2).Power(127).Sub(math.LegacyOneDec()) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - sink, _ = b1.ApproxSqrt() - } - - if sink == nil { - b.Fatal("Benchmark did not run") - } - sink = (interface{})(nil) +func TestMulExactGood(t *testing.T) { + a, err := NewDecFromString("1.000001") + require.NoError(t, err) + b := NewDecWithPrec(1, 6) + c, err := a.MulExact(b) + require.NoError(t, err) + d, err := c.Int64() + require.NoError(t, err) + require.Equal(t, int64(1000001), d) } -func BenchmarkLegacyQuoRoundupMut(b *testing.B) { - b1 := math.LegacyNewDec(17e2 + 8371) - baseArr := make([]math.LegacyDec, b.N) - for i := 0; i < b.N; i++ { - baseArr[i] = b1.Clone() - } - b2 := math.LegacyNewDec(4371) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - sink = baseArr[i].QuoRoundupMut(b2) - } - - if sink == nil { - b.Fatal("Benchmark did not run") - } - sink = (interface{})(nil) +func TestMulExactBad(t *testing.T) { + a, err := NewDecFromString("1.000000000000000000000000000000000000123456789") + require.NoError(t, err) + b := NewDecWithPrec(1, 10) + _, err = a.MulExact(b) + require.ErrorIs(t, err, ErrUnexpectedRounding) } -func TestFormatDec(t *testing.T) { - type decimalTest []string - var testcases []decimalTest - raw, err := os.ReadFile("./testdata/decimals.json") +func TestQuoExactGood(t *testing.T) { + a, err := NewDecFromString("1000001") require.NoError(t, err) - err = json.Unmarshal(raw, &testcases) + b := NewDecWithPrec(1, 6) + c, err := a.QuoExact(b) require.NoError(t, err) + require.Equal(t, "1.000001000000000000000000000000000", c.String()) +} - for _, tc := range testcases { - tc := tc - t.Run(tc[0], func(t *testing.T) { - out, err := math.FormatDec(tc[0]) - require.NoError(t, err) - require.Equal(t, tc[1], out) - }) +func TestQuoExactBad(t *testing.T) { + a, err := NewDecFromString("1000000000000000000000000000000000000123456789") + require.NoError(t, err) + b := NewDecWithPrec(1, 10) + _, err = a.QuoExact(b) + require.ErrorIs(t, err, ErrUnexpectedRounding) +} + +func TestToBigInt(t *testing.T) { + i1 := "1000000000000000000000000000000000000123456789" + tcs := []struct { + intStr string + out string + isError error + }{ + {i1, i1, nil}, + {"1000000000000000000000000000000000000123456789.00000000", i1, nil}, + {"123.456e6", "123456000", nil}, + {"12345.6", "", ErrNonIntegeral}, + } + for idx, tc := range tcs { + a, err := NewDecFromString(tc.intStr) + require.NoError(t, err) + b, err := a.BigInt() + if tc.isError == nil { + require.NoError(t, err, "test_%d", idx) + require.Equal(t, tc.out, b.String(), "test_%d", idx) + } else { + require.ErrorIs(t, err, tc.isError, "test_%d", idx) + } } } -func TestFormatDecNonDigits(t *testing.T) { - badCases := []string{ - "10.a", - "1a.10", - "p1a10.", - "0.10p", - "--10", - "12.😎😎", - "11111111111133333333333333333333333333333a", - "11111111111133333333333333333333333333333 192892", +func TestToSdkInt(t *testing.T) { + i1 := "1000000000000000000000000000000000000123456789" + tcs := []struct { + intStr string + out string + }{ + {i1, i1}, + {"1000000000000000000000000000000000000123456789.00000000", i1}, + {"123.456e6", "123456000"}, + {"123.456e1", "1234"}, + {"123.456", "123"}, + {"123.956", "123"}, + {"-123.456", "-123"}, + {"-123.956", "-123"}, + {"-0.956", "0"}, + {"-0.9", "0"}, } - - for _, value := range badCases { - value := value - t.Run(value, func(t *testing.T) { - s, err := math.FormatDec(value) - if err == nil { - t.Fatal("Expected an error") - } - if g, w := err.Error(), "non-digits"; !strings.Contains(g, w) { - t.Errorf("Error mismatch\nGot: %q\nWant substring: %q", g, w) - } - if s != "" { - t.Fatalf("Got a non-empty string: %q", s) - } - }) + for idx, tc := range tcs { + a, err := NewDecFromString(tc.intStr) + require.NoError(t, err) + b := a.SdkIntTrim() + require.Equal(t, tc.out, b.String(), "test_%d", idx) } } -func TestNegativePrecisionPanic(t *testing.T) { - require.Panics(t, func() { - math.LegacyNewDecWithPrec(10, -1) - }) -} - -func (s *decimalTestSuite) TestConvertToBigIntMutativeForLegacyDec() { - r := big.NewInt(30) - i := math.LegacyNewDecFromBigInt(r) - - // Compare value of BigInt & BigIntMut - s.Require().Equal(i.BigInt(), i.BigIntMut()) - - // Modify BigIntMut() pointer and ensure i.BigIntMut() & i.BigInt() change - p1 := i.BigIntMut() - p1.SetInt64(40) - s.Require().Equal(big.NewInt(40), i.BigIntMut()) - s.Require().Equal(big.NewInt(40), i.BigInt()) - - // Modify big.Int() pointer and ensure i.BigIntMut() & i.BigInt() don't change - p2 := i.BigInt() - p2.SetInt64(50) - s.Require().NotEqual(big.NewInt(50), i.BigIntMut()) - s.Require().NotEqual(big.NewInt(50), i.BigInt()) +func TestInfDecString(t *testing.T) { + _, err := NewDecFromString("iNf") + require.Error(t, err) + require.ErrorIs(t, err, ErrInfiniteString) } -func TestQuoMut(t *testing.T) { - specs := map[string]struct { - dividend, divisor math.LegacyDec - expTruncated, expRoundedUp string - expPanic bool - }{ - "0.0000000000000000001": { - dividend: math.LegacyNewDecWithPrec(1, 18), - divisor: math.LegacyMustNewDecFromStr("10"), - expRoundedUp: "0.000000000000000001", - expTruncated: "0.000000000000000000", - }, - "0.0000000000000000002": { - dividend: math.LegacyNewDecWithPrec(1, 18), - divisor: math.LegacyMustNewDecFromStr("5"), - expRoundedUp: "0.000000000000000001", - expTruncated: "0.000000000000000000", - }, - "0.0000000000000000003": { - dividend: math.LegacyNewDecWithPrec(1, 18), - divisor: math.LegacyMustNewDecFromStr("3.333333333333333"), - expRoundedUp: "0.000000000000000001", - expTruncated: "0.000000000000000000", - }, - "0.0000000000000000004": { - dividend: math.LegacyNewDecWithPrec(1, 18), - divisor: math.LegacyMustNewDecFromStr("2.5"), - expRoundedUp: "0.000000000000000001", - expTruncated: "0.000000000000000000", - }, - "0.0000000000000000005": { - dividend: math.LegacyNewDecWithPrec(1, 18), - divisor: math.LegacyMustNewDecFromStr("2"), - expRoundedUp: "0.000000000000000001", - - expTruncated: "0.000000000000000000", - }, - "0.0000000000000000006": { - dividend: math.LegacyNewDecWithPrec(1, 18), - divisor: math.LegacyMustNewDecFromStr("1.666666666666666666"), - expRoundedUp: "0.000000000000000001", - - expTruncated: "0.000000000000000000", - }, - "0.0000000000000000007": { - dividend: math.LegacyNewDecWithPrec(1, 18), - divisor: math.LegacyMustNewDecFromStr("1.428571428571429"), - expRoundedUp: "0.000000000000000001", - - expTruncated: "0.000000000000000000", - }, - "0.0000000000000000008": { - dividend: math.LegacyNewDecWithPrec(1, 18), - divisor: math.LegacyMustNewDecFromStr("1.25"), - expRoundedUp: "0.000000000000000001", - - expTruncated: "0.000000000000000000", - }, - "0.0000000000000000009": { - dividend: math.LegacyNewDecWithPrec(1, 18), - divisor: math.LegacyMustNewDecFromStr("1.111111111111111"), - expRoundedUp: "0.000000000000000001", +//func TestDecToLegacyDec(t *testing.T) { +// dec := NewDecFromInt64(123) +// +// legacyDec, err := DecToLegacyDec(dec) +// require.NoError(t, err) +// +// expected, _ := LegacyNewDecFromStr("123.000000000000000000") +// require.True(t, legacyDec.Equal(expected)) +//} - expTruncated: "0.000000000000000000", - }, - "-0.0000000000000000001": { - dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), - divisor: math.LegacyMustNewDecFromStr("10"), - expRoundedUp: "0.000000000000000000", - expTruncated: "0.000000000000000000", - }, - "-0.0000000000000000002": { - dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), - divisor: math.LegacyMustNewDecFromStr("5"), - expRoundedUp: "0.000000000000000000", - expTruncated: "0.000000000000000000", - }, - "-0.0000000000000000003": { - dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), - divisor: math.LegacyMustNewDecFromStr("3.333333333333333"), - expRoundedUp: "0.000000000000000000", - expTruncated: "0.000000000000000000", - }, - "-0.0000000000000000004": { - dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), - divisor: math.LegacyMustNewDecFromStr("2.5"), - expRoundedUp: "0.000000000000000000", - expTruncated: "0.000000000000000000", - }, - "-0.0000000000000000005": { - dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), - divisor: math.LegacyMustNewDecFromStr("2"), - expRoundedUp: "0.000000000000000000", - expTruncated: "0.000000000000000000", - }, - "-0.0000000000000000006": { - dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), - divisor: math.LegacyMustNewDecFromStr("1.666666666666666666"), - expRoundedUp: "0.000000000000000000", - expTruncated: "0.000000000000000000", - }, - "-0.0000000000000000007": { - dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), - divisor: math.LegacyMustNewDecFromStr("1.428571428571429"), - expRoundedUp: "0.000000000000000000", - expTruncated: "0.000000000000000000", - }, - "-0.0000000000000000008": { - dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), - divisor: math.LegacyMustNewDecFromStr("1.25"), - expRoundedUp: "0.000000000000000000", - expTruncated: "0.000000000000000000", - }, - "-0.0000000000000000009": { - dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), - divisor: math.LegacyMustNewDecFromStr("1.111111111111111"), - expRoundedUp: "0.000000000000000000", - expTruncated: "0.000000000000000000", - }, - "--0.0000000000000000001": { - dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), - divisor: math.LegacyMustNewDecFromStr("-10"), - expRoundedUp: "0.000000000000000001", - expTruncated: "0.000000000000000000", - }, - "--0.0000000000000000002": { - dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), - divisor: math.LegacyMustNewDecFromStr("-5"), - expRoundedUp: "0.000000000000000001", - expTruncated: "0.000000000000000000", - }, - "--0.0000000000000000003": { - dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), - divisor: math.LegacyMustNewDecFromStr("-3.333333333333333"), - expRoundedUp: "0.000000000000000001", - expTruncated: "0.000000000000000000", - }, - "--0.0000000000000000004": { - dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), - divisor: math.LegacyMustNewDecFromStr("-2.5"), - expRoundedUp: "0.000000000000000001", - expTruncated: "0.000000000000000000", - }, - "--0.0000000000000000005": { - dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), - divisor: math.LegacyMustNewDecFromStr("-2"), - expRoundedUp: "0.000000000000000001", - expTruncated: "0.000000000000000000", - }, - "--0.0000000000000000006": { - dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), - divisor: math.LegacyMustNewDecFromStr("-1.666666666666666666"), - expRoundedUp: "0.000000000000000001", - expTruncated: "0.000000000000000000", - }, - "--0.0000000000000000007": { - dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), - divisor: math.LegacyMustNewDecFromStr("-1.428571428571429"), - expRoundedUp: "0.000000000000000001", - expTruncated: "0.000000000000000000", - }, - "--0.0000000000000000008": { - dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), - divisor: math.LegacyMustNewDecFromStr("-1.25"), - expRoundedUp: "0.000000000000000001", - expTruncated: "0.000000000000000000", - }, - "--0.0000000000000000009": { - dividend: math.LegacyNewDecWithPrec(1, 18).Neg(), - divisor: math.LegacyMustNewDecFromStr("-1.111111111111111"), - expRoundedUp: "0.000000000000000001", - expTruncated: "0.000000000000000000", - }, - "big / small": { - dividend: math.LegacyMustNewDecFromStr("999999999999999999"), - divisor: math.LegacyNewDecWithPrec(1, 18), - expRoundedUp: "999999999999999999000000000000000000.000000000000000000", - expTruncated: "999999999999999999000000000000000000.000000000000000000", - }, - "divide by dividend": { - dividend: math.LegacyNewDecWithPrec(123, 0), - divisor: math.LegacyMustNewDecFromStr("123"), - expRoundedUp: "1.000000000000000000", - expTruncated: "1.000000000000000000", - }, - "zero divided": { - dividend: math.LegacyNewDecWithPrec(0, 0), - divisor: math.LegacyMustNewDecFromStr("1"), - expRoundedUp: "0.000000000000000000", - expTruncated: "0.000000000000000000", - }, - "zero divided by negative value": { - dividend: math.LegacyNewDecWithPrec(0, 0), - divisor: math.LegacyMustNewDecFromStr("-1"), - expRoundedUp: "0.000000000000000000", - expTruncated: "0.000000000000000000", - }, - "zero divided by zero": { - dividend: math.LegacyNewDecWithPrec(0, 0), - divisor: math.LegacyMustNewDecFromStr("0"), - expPanic: true, - }, - "divide by zero": { - dividend: math.LegacyNewDecWithPrec(1, 0), - divisor: math.LegacyMustNewDecFromStr("0"), - expPanic: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - t.Run("round up", func(t *testing.T) { - t.Parallel() - if !spec.expPanic { - got := spec.dividend.Clone().QuoRoundupMut(spec.divisor.Clone()) - require.Equal(t, spec.expRoundedUp, got.String()) - return - } - require.Panics(t, func() { - _ = spec.dividend.Clone().QuoRoundupMut(spec.divisor.Clone()) - }) - }) - t.Run("truncate", func(t *testing.T) { - t.Parallel() - if !spec.expPanic { - got := spec.dividend.Clone().QuoTruncateMut(spec.divisor.Clone()) - require.Equal(t, spec.expTruncated, got.String()) - return - } - require.Panics(t, func() { - _ = spec.dividend.Clone().QuoTruncateMut(spec.divisor.Clone()) - }) - }) - }) +func must[T any](r T, err error) T { + if err != nil { + panic(err) } + return r } diff --git a/math/doc.go b/math/doc.go index 17664a9531e0..c3ea3346ddf6 100644 --- a/math/doc.go +++ b/math/doc.go @@ -2,5 +2,11 @@ Package math implements custom Cosmos SDK math types used for arithmetic operations. Signed and unsigned integer types utilize Golang's standard library big integers types, having a maximum bit length of 256 bits. + +This code is based on the implementation found in the Regen Ledger project: +https://github.com/regen-network/regen-ledger/tree/main/types/math + +Co-authors and contributors include: +Regen Network */ package math diff --git a/math/go.mod b/math/go.mod index 53100ea6e7bb..bc3e2b925527 100644 --- a/math/go.mod +++ b/math/go.mod @@ -1,19 +1,43 @@ module cosmossdk.io/math -go 1.20 +go 1.21 + +toolchain go1.22.2 require ( + github.com/cockroachdb/apd/v3 v3.2.1 github.com/stretchr/testify v1.9.0 - golang.org/x/exp v0.0.0-20221205204356-47842c84f3db + golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 sigs.k8s.io/yaml v1.4.0 ) require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/kr/pretty v0.3.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect + github.com/cometbft/cometbft v0.38.5 // indirect + github.com/cosmos/gogoproto v1.4.11 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a // indirect + github.com/petermattis/goid v0.0.0-20230904192822-1876fd5063bc // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/sasha-s/go-deadlock v0.3.1 // indirect + golang.org/x/crypto v0.19.0 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c // indirect + google.golang.org/grpc v1.62.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect +) + +require ( + cosmossdk.io/errors v1.0.1 + github.com/cosmos/cosmos-sdk v0.50.5 + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + pgregory.net/rapid v1.1.0 ) // Issue with math.Int{}.Size() implementation. diff --git a/math/go.sum b/math/go.sum index a6d9afc96b85..178bb12faea9 100644 --- a/math/go.sum +++ b/math/go.sum @@ -1,28 +1,73 @@ -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +cosmossdk.io/errors v1.0.1 h1:bzu+Kcr0kS/1DuPBtUFdWjzLqyUuCiyHjyJB6srBV/0= +cosmossdk.io/errors v1.0.1/go.mod h1:MeelVSZThMi4bEakzhhhE/CKqVv3nOJDA25bIqRDu/U= +github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= +github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ= +github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= +github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= +github.com/cometbft/cometbft v0.38.5 h1:4lOcK5VTPrfbLOhNHmPYe6c7eDXHtBdMCQuKbAfFJdU= +github.com/cometbft/cometbft v0.38.5/go.mod h1:0tqKin+KQs8zDwzYD8rPHzSBIDNPuB4NrwwGDNb/hUg= +github.com/cosmos/cosmos-sdk v0.50.5 h1:MOEi+DKYgW67YaPgB+Pf+nHbD3V9S/ayitRKJYLfGIA= +github.com/cosmos/cosmos-sdk v0.50.5/go.mod h1:oV/k6GJgXV9QPoM2fsYDPPsyPBgQbdotv532O6Mz1OQ= +github.com/cosmos/gogoproto v1.4.11 h1:LZcMHrx4FjUgrqQSWeaGC1v/TeuVFqSLa43CC6aWR2g= +github.com/cosmos/gogoproto v1.4.11/go.mod h1:/g39Mh8m17X8Q/GDEs5zYTSNaNnInBSohtaxzQnYq1Y= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= +github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a h1:dlRvE5fWabOchtH7znfiFCcOvmIYgOeAS5ifBXBlh9Q= +github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= +github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= +github.com/petermattis/goid v0.0.0-20230904192822-1876fd5063bc h1:8bQZVK1X6BJR/6nYUPxQEP+ReTsceJTKizeuwjWOPUA= +github.com/petermattis/goid v0.0.0-20230904192822-1876fd5063bc/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0= +github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= -golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c h1:NUsgEN92SQQqzfA+YtqYNqYmB3DMMYLlIwUZAQFVFbo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= +google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk= +google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw= +pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/math/math.go b/math/math.go new file mode 100644 index 000000000000..bfee6a5f46e3 --- /dev/null +++ b/math/math.go @@ -0,0 +1,73 @@ +// Package math provides helper functions for doing mathematical calculations and parsing for the ecocredit module. +package math + +import ( + "fmt" + + "cosmossdk.io/errors" + "github.com/cockroachdb/apd/v3" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +var exactContext = apd.Context{ + Precision: 0, + MaxExponent: apd.MaxExponent, + MinExponent: apd.MinExponent, + Traps: apd.DefaultTraps | apd.Inexact | apd.Rounded, +} + +// Add adds x and y +func Add(x Dec, y Dec) (Dec, error) { + return x.Add(y) +} + +// 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 +} + +// SafeSubBalance subtracts the value of y from x and returns the result with arbitrary precision. +// Returns with ErrInsufficientFunds error if the result is negative. +func SafeSubBalance(x Dec, y Dec) (Dec, error) { + var z Dec + _, err := exactContext.Sub(&z.dec, &x.dec, &y.dec) + if err != nil { + return z, errors.Wrap(err, "decimal subtraction error") + } + + if z.IsNegative() { + return z, sdkerrors.ErrInsufficientFunds + } + + return z, nil +} + +// SafeAddBalance adds the value of x+y and returns the result with arbitrary precision. +// Returns with ErrInvalidRequest error if either x or y is negative. +func SafeAddBalance(x Dec, y Dec) (Dec, error) { + var z Dec + + if x.IsNegative() || y.IsNegative() { + return z, errors.Wrap( + sdkerrors.ErrInvalidRequest, + fmt.Sprintf("AddBalance() requires two non-negative Dec parameters, but received %s and %s", x, y)) + } + + _, err := exactContext.Add(&z.dec, &x.dec, &y.dec) + if err != nil { + return z, errors.Wrap(err, "decimal subtraction error") + } + + return z, nil +} diff --git a/x/group/go.mod b/x/group/go.mod index a7673b91e87c..8e84c9c180f8 100644 --- a/x/group/go.mod +++ b/x/group/go.mod @@ -18,7 +18,7 @@ require ( cosmossdk.io/x/gov v0.0.0-20230925135524-a1bc045b3190 cosmossdk.io/x/mint v0.0.0-00010101000000-000000000000 cosmossdk.io/x/staking v0.0.0-00010101000000-000000000000 - github.com/cockroachdb/apd/v2 v2.0.2 + github.com/cockroachdb/apd/v3 v3.2.1 github.com/cometbft/cometbft v1.0.0-alpha.2.0.20240530055211-ae27f7eb3c08 github.com/cosmos/cosmos-db v1.0.2 github.com/cosmos/cosmos-proto v1.0.0-beta.5 diff --git a/x/group/go.sum b/x/group/go.sum index 321194383aea..31038d27c7f6 100644 --- a/x/group/go.sum +++ b/x/group/go.sum @@ -79,8 +79,8 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E= -github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= +github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= +github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8= diff --git a/x/group/internal/math/dec.go b/x/group/internal/math/dec.go index 02a0ca847678..72203a39863c 100644 --- a/x/group/internal/math/dec.go +++ b/x/group/internal/math/dec.go @@ -4,10 +4,10 @@ package math import ( "fmt" - "github.com/cockroachdb/apd/v2" - errorsmod "cosmossdk.io/errors" "cosmossdk.io/x/group/errors" + + "github.com/cockroachdb/apd/v3" ) // Dec is a wrapper struct around apd.Decimal that does no mutation of apd.Decimal's when performing