From edcb427a6e0c38ca0d4b0f5ab64852e50d5d74ea Mon Sep 17 00:00:00 2001 From: Tyler <48813565+technicallyty@users.noreply.github.com> Date: Fri, 14 Feb 2025 09:05:23 -0800 Subject: [PATCH] chore: set math to main (#23697) --- math/CHANGELOG.md | 71 +- math/dec.go | 1330 ++++------- math/dec_bench_test.go | 353 +++ math/dec_examples_test.go | 323 +++ math/dec_rapid_test.go | 527 +++++ math/dec_test.go | 2101 +++++++++++------ math/go.mod | 38 +- math/go.sum | 51 +- math/int.go | 174 +- math/int_test.go | 201 +- math/legacy_dec.go | 971 ++++++++ .../{fuzz_test.go => legacy_dec_fuzz_test.go} | 6 - ...al_test.go => legacy_dec_internal_test.go} | 1 - math/legacy_dec_test.go | 1328 +++++++++++ math/max_min.go | 4 +- math/sonar-project.properties | 16 + math/uint.go | 38 +- math/uint_test.go | 35 +- 18 files changed, 5911 insertions(+), 1657 deletions(-) create mode 100644 math/dec_bench_test.go create mode 100644 math/dec_examples_test.go create mode 100644 math/dec_rapid_test.go create mode 100644 math/legacy_dec.go rename math/{fuzz_test.go => legacy_dec_fuzz_test.go} (73%) rename math/{dec_internal_test.go => legacy_dec_internal_test.go} (99%) create mode 100644 math/legacy_dec_test.go create mode 100644 math/sonar-project.properties diff --git a/math/CHANGELOG.md b/math/CHANGELOG.md index 10dbe4a437..84b6452929 100644 --- a/math/CHANGELOG.md +++ b/math/CHANGELOG.md @@ -36,10 +36,79 @@ Ref: https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.j ## [Unreleased] +## [math/v1.5.0](https://github.com/cosmos/cosmos-sdk/releases/tag/math/v1.5.0) - 2025-01-06 + +* [#11783](https://github.com/cosmos/cosmos-sdk/issues/11783) Upstream GDA based decimal type + +## [math/v1.4.0](https://github.com/cosmos/cosmos-sdk/releases/tag/math/v1.4.0) - 2024-11-20 + +### Features + +* [#20034](https://github.com/cosmos/cosmos-sdk/pull/20034) Significantly speedup LegacyDec.QuoTruncate and LegacyDec.QuoRoundUp. + +### Bug fixes + +* Fix [ASA-2024-010: Math](https://github.com/cosmos/cosmos-sdk/security/advisories/GHSA-7225-m954-23v7) Bit length differences between Int and Dec + + +## [math/v1.3.0](https://github.com/cosmos/cosmos-sdk/releases/tag/math/v1.3.0) - 2024-02-22 + +### Features + +* [#18552](https://github.com/cosmos/cosmos-sdk/pull/18552) Add safe arithmetic operations for `math.Int` that return an error in case of an overflow or any mishap. +* [#18421](https://github.com/cosmos/cosmos-sdk/pull/18421) Add mutative api for `LegacyDec.BigInt()`. +* [#18874](https://github.com/cosmos/cosmos-sdk/pull/18874) Speedup `math.Int.Mul` by removing a duplicate overflow check +* [#19386](https://github.com/cosmos/cosmos-sdk/pull/19386) Speedup `math.Int` overflow checks +* [#19466](https://github.com/cosmos/cosmos-sdk/pull/19466) Speedup `math.NewLegacyDec` by one heap allocation +* [#19467](https://github.com/cosmos/cosmos-sdk/pull/19467) Slightly speedup `math.LegacyDec` `Ceil` and `MarshalTo` methods +* [#19479](https://github.com/cosmos/cosmos-sdk/pull/19479) Speedup `math.LegacyDec` functions that involve `math.Int` by removing a heap allocation. (`Ceil`, `TruncateInt`, `NewLegacyDecFromInt`) + ### Bug Fixes -* [#16266](https://github.com/cosmos/cosmos-sdk/pull/16266) fix: legacy dec power mut zero exponent precision. +* [#18519](https://github.com/cosmos/cosmos-sdk/pull/18519) Prevent Overflow in `Dec.Ceil()`. + +## [math/v1.2.0](https://github.com/cosmos/cosmos-sdk/releases/tag/math/v1.2.0) - 2023-11-07 + +### Features + +* [#18247](https://github.com/cosmos/cosmos-sdk/pull/18247) Add mutative api for `Uint.BigInt()`. +* [#17803](https://github.com/cosmos/cosmos-sdk/pull/17803) Add mutative api for `Int.BigInt()`. +* [#18030](https://github.com/cosmos/cosmos-sdk/pull/18030) Add mutative api for `NewIntFromBigInt`. + +### Bug Fixes + +* [#18228](https://github.com/cosmos/cosmos-sdk/pull/18228) Fix panic when calling `BigInt()` on an uninitialized `Uint`. +* [#18214](https://github.com/cosmos/cosmos-sdk/pull/18214) Ensure that modifying the argument to `NewUIntFromBigInt` doesn't mutate the returned value. * [#18211](https://github.com/cosmos/cosmos-sdk/pull/18211) RelativePow now returns 1 when 0^0, before it was returning the scale factor. +* [#17725](https://github.com/cosmos/cosmos-sdk/pull/17725) Fix state break in ApproxRoot. This has been present since math/v1.0.1. It changed the rounding behavior at precision end in an intermediary division from banker's to truncation. The truncation occurs from binary right shift in the case of square roots. The change is now reverted back to banker's rounding universally for any root. + +## [math/v1.1.2](https://github.com/cosmos/cosmos-sdk/releases/tag/math/v1.1.2) - 2023-08-21 + +### Bug Fixes + +* [#17489](https://github.com/cosmos/cosmos-sdk/pull/17489) Revert [#16263](https://github.com/cosmos/cosmos-sdk/pull/16263). + +## [math/v1.1.1](https://github.com/cosmos/cosmos-sdk/releases/tag/math/v1.1.1) - 2023-08-21 + +### Bug Fixes + +* [#17480](https://github.com/cosmos/cosmos-sdk/pull/17480) Fix panic when calling `.Size()` on a nil `math.Int` value. + +## [math/v1.1.0](https://github.com/cosmos/cosmos-sdk/releases/tag/math/v1.1.0) - 2023-08-19 + +### Features + +* [#17427](https://github.com/cosmos/cosmos-sdk/pull/17427) Implement LegacyDec.MulRoundUp that rounds up at precision end. + +### Improvements + +* [#17109](https://github.com/cosmos/cosmos-sdk/pull/17109) Add `.ToLegacyDec()` method on `math.Int` type for converting to `math.LegacyDec`. +* [#16263](https://github.com/cosmos/cosmos-sdk/pull/16263) Improved `math/Int.Size` by computing the decimal digits count instead of firstly invoking .Marshal() then checking the length + +### Bug Fixes + +* [#17352](https://github.com/cosmos/cosmos-sdk/pull/17352) Ensure that modifying the argument to `NewIntFromBigInt` doesn't mutate the returned value. +* [#16266](https://github.com/cosmos/cosmos-sdk/pull/16266) Fix legacy dec power mut zero exponent precision. ## [math/v1.0.1](https://github.com/cosmos/cosmos-sdk/releases/tag/math/v1.0.1) - 2023-05-15 diff --git a/math/dec.go b/math/dec.go index d92b0042f3..4935fb2eb9 100644 --- a/math/dec.go +++ b/math/dec.go @@ -2,949 +2,521 @@ package math import ( "encoding/json" - "errors" - "fmt" + stderrors "errors" "math/big" "strconv" - "strings" - "testing" + + "github.com/cockroachdb/apd/v3" + + "cosmossdk.io/errors" ) -// NOTE: never use new(Dec) or else we will panic unmarshalling into the -// nil embedded big.Int -type LegacyDec struct { - i *big.Int -} +var _ customProtobufType = &Dec{} const ( - // number of decimal places - LegacyPrecision = 18 - - // 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 - - // max number of iterations in ApproxRoot function - maxApproxRootIterations = 300 + // MaxExponent is the highest exponent supported. Exponents near this range will + // perform very slowly (many seconds per operation). + MaxExponent = apd.MaxExponent + // MinExponent is the lowest exponent supported with the same limitations as + // MaxExponent. + MinExponent = apd.MinExponent ) +// 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 +} + +const mathCodespace = "math" + 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) + ErrInvalidDec = errors.Register(mathCodespace, 1, "invalid decimal") + ErrUnexpectedRounding = errors.Register(mathCodespace, 2, "unexpected rounding") + ErrNonIntegral = errors.Register(mathCodespace, 3, "value is non-integral") ) -// 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)) - } +// 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: MaxExponent, + MinExponent: MinExponent, + Traps: apd.DefaultTraps, } -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] -} - -// create a new Dec from integer assuming whole number -func LegacyNewDec(i int64) LegacyDec { - return LegacyNewDecWithPrec(i, 0) -} - -// create a new Dec from integer with decimal place at prec -// CONTRACT: prec <= Precision -func LegacyNewDecWithPrec(i, prec int64) LegacyDec { - return LegacyDec{ - new(big.Int).Mul(big.NewInt(i), precisionMultiplier(prec)), - } -} - -// create a new Dec from big integer assuming whole numbers -// CONTRACT: prec <= Precision -func LegacyNewDecFromBigInt(i *big.Int) LegacyDec { - return LegacyNewDecFromBigIntWithPrec(i, 0) -} - -// 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)), - } -} - -// create a new Dec from big integer assuming whole numbers -// CONTRACT: prec <= Precision -func LegacyNewDecFromInt(i Int) LegacyDec { - return LegacyNewDecFromIntWithPrec(i, 0) -} - -// 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.BigInt(), precisionMultiplier(prec)), - } -} - -// create a decimal from an input decimal string. -// valid must come in the form: +// NewDecFromString converts a string to a Dec type, supporting standard, scientific, and negative notations. +// It handles non-numeric values and overflow conditions, returning errors for invalid inputs like "NaN" or "Infinity". // -// (-) whole integers (.) decimal integers +// Examples: +// - "123" -> Dec{123} +// - "-123.456" -> Dec{-123.456} +// - "1.23E4" -> Dec{12300} +// - "NaN" or "Infinity" -> ErrInvalidDec // -// examples of acceptable input include: +// The internal representation is an arbitrary-precision decimal: Negative × Coeff × 10*Exponent +// The maximum exponent is 100_000 and must not be exceeded. Following values would be invalid: +// 1E100001 -> ErrInvalidDec +// -1E100001 -> ErrInvalidDec +// 1E-100001 -> ErrInvalidDec // -// -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:] +// This function is essential for converting textual data into Dec types for numerical operations. +func NewDecFromString(s string) (Dec, error) { + d, _, err := apd.NewFromString(s) + if err != nil { + return Dec{}, ErrInvalidDec.Wrap(err.Error()) } - if len(str) == 0 { - return LegacyDec{}, ErrLegacyEmptyDecimalStr + switch d.Form { + case apd.NaN, apd.NaNSignaling: + return Dec{}, ErrInvalidDec.Wrap("not a number") + case apd.Infinite: + return Dec{}, ErrInvalidDec.Wrap(s) + case apd.Finite: + result := Dec{*d} + return result, nil + default: + return Dec{}, ErrInvalidDec.Wrapf("unsupported type: %d", d.Form) + } +} + +// NewDecFromInt64 converts an int64 to a Dec type. +// This function is useful for creating Dec values from integer literals or variables, +// ensuring they can be used in high-precision arithmetic operations defined for Dec types. +// +// Example: +// - NewDecFromInt64(123) returns a Dec representing the value 123. +func NewDecFromInt64(x int64) Dec { + var res Dec + res.dec.SetInt64(x) + return res +} + +// NewDecWithExp creates a Dec from a coefficient and exponent, calculated as coeff * 10^exp. +// Useful for precise decimal representations. +// Although this method can be used with a higher than maximum exponent or lower than minimum exponent, further arithmetic +// or other method may fail. +// +// Example: +// - NewDecWithExp(123, -2) -> Dec representing 1.23. +func NewDecWithExp(coeff int64, exp int32) Dec { + var res Dec + res.dec.SetFinite(coeff, exp) + return res +} + +// Add returns a new Dec representing the sum of `x` and `y` using returning a new Dec, we use apd.BaseContext. +// This function ensures that no arguments are mutated during the operation and checks for overflow conditions. +// If an overflow occurs, an error is returned. +// +// The precision is much higher as long as the max exponent is not exceeded. If the max exponent is exceeded, an error is returned. +// For example: +// - 1e100000 + -1e-1 +// - 1e100000 + 9e100000 +// - 1e100001 + 0 +// We can see that in apd.BaseContext the max exponent is defined hence we cannot exceed. +// +// This function wraps any internal errors with a context-specific error message for clarity. +func (x Dec) Add(y Dec) (Dec, error) { + var z Dec + _, err := apd.BaseContext.Add(&z.dec, &x.dec, &y.dec) + if err != nil { + return Dec{}, ErrInvalidDec.Wrap(err.Error()) } - strs := strings.Split(str, ".") - lenDecs := 0 - combinedStr := strs[0] + return z, nil +} - if len(strs) == 2 { // has a decimal place - lenDecs = len(strs[1]) - if lenDecs == 0 || len(combinedStr) == 0 { - return LegacyDec{}, ErrLegacyInvalidDecimalLength +// Sub returns a new Dec representing the sum of `x` and `y` using returning a new Dec, we use apd.BaseContext. +// This function ensures that no arguments are mutated during the operation and checks for overflow conditions. +// If an overflow occurs, an error is returned. +// +// The precision is much higher as long as the max exponent is not exceeded. If the max exponent is exceeded, an error is returned. +// For example: +// - 1e-100001 - 0 +// - 1e100000 - 1e-1 +// - 1e100000 - -9e100000 +// - 1e100001 - 1e100001 (upper limit exceeded) +// - 1e-100001 - 1e-100001 (lower limit exceeded) +// We can see that in apd.BaseContext the max exponent is defined hence we cannot exceed. +// +// This function wraps any internal errors with a context-specific error message for clarity. +func (x Dec) Sub(y Dec) (Dec, error) { + var z Dec + _, err := apd.BaseContext.Sub(&z.dec, &x.dec, &y.dec) + if err != nil { + if err2 := stderrors.Unwrap(err); err2 != nil { + // use unwrapped error to not return "add:" prefix from raw apd error + err = err2 } - combinedStr += strs[1] - } else if len(strs) > 2 { - return LegacyDec{}, ErrLegacyInvalidDecimalStr + return Dec{}, ErrInvalidDec.Wrap("sub: " + err.Error()) + } + return z, nil +} + +// Quo performs division of x by y using the decimal128 context with 34 digits of precision. +// It returns a new Dec or an error if the division is not feasible due to constraints of decimal128. +// +// Within Quo half up rounding may be performed to match the defined precision. If this is unwanted, QuoExact +// should be used instead. +// +// Key error scenarios: +// - Division by zero (e.g., `123 / 0` or `0 / 0`) results in ErrInvalidDec. +// - Non-representable values due to extreme ratios or precision limits. +// +// Examples: +// - `0 / 123` yields `0`. +// - `123 / 123` yields `1.000000000000000000000000000000000`. +// - `-123 / 123` yields `-1.000000000000000000000000000000000`. +// - `4 / 9` yields `0.4444444444444444444444444444444444`. +// - `5 / 9` yields `0.5555555555555555555555555555555556`. +// - `6 / 9` yields `0.6666666666666666666666666666666667`. +// - `1e-100000 / 10` yields error. +// +// This function is non-mutative and enhances error clarity with specific messages. +func (x Dec) Quo(y Dec) (Dec, error) { + var z Dec + _, err := dec128Context.Quo(&z.dec, &x.dec, &y.dec) + if err != nil { + return Dec{}, ErrInvalidDec.Wrap(err.Error()) } - if lenDecs > LegacyPrecision { - return LegacyDec{}, fmt.Errorf("value '%s' exceeds max precision by %d decimal places: max precision %d", str, LegacyPrecision-lenDecs, LegacyPrecision) + return z, errors.Wrap(err, "decimal quotient error") +} + +// QuoExact performs division like Quo and additionally checks for rounding. It returns ErrUnexpectedRounding if +// any rounding occurred during the division. If the division is exact, it returns the result without error. +// +// This function is particularly useful in financial calculations or other scenarios where precision is critical +// and rounding could lead to significant errors. +// +// Key error scenarios: +// - Division by zero (e.g., `123 / 0` or `0 / 0`) results in ErrInvalidDec. +// - Rounding would have occurred, which is not permissible in this context, resulting in ErrUnexpectedRounding. +// +// Examples: +// - `0 / 123` yields `0` without rounding. +// - `123 / 123` yields `1.000000000000000000000000000000000` exactly. +// - `-123 / 123` yields `-1.000000000000000000000000000000000` exactly. +// - `1 / 9` yields error for the precision limit +// - `1e-100000 / 10` yields error for crossing the lower exponent limit. +// - Any division resulting in a non-terminating decimal under decimal128 precision constraints triggers ErrUnexpectedRounding. +// +// This function does not mutate any arguments and wraps any internal errors with a context-specific error message for clarity. +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, ErrInvalidDec.Wrap(err.Error()) + } + if condition.Rounded() { + return z, ErrUnexpectedRounding + } + return z, errors.Wrap(err, "decimal quotient error") +} + +// QuoInteger performs integer division of x by y, returning a new Dec formatted as decimal128 with 34 digit precision. +// This function returns the integer part of the quotient, discarding any fractional part, and is useful in scenarios +// where only the whole number part of the division result is needed without rounding. +// +// Key error scenarios: +// - Division by zero (e.g., `123 / 0`) results in ErrInvalidDec. +// - Overflow conditions if the result exceeds the storage capacity of a decimal128 formatted number. +// +// Examples: +// - `123 / 50` yields `2` (since the fractional part .46 is discarded). +// - `100 / 3` yields `33` (since the fractional part .3333... is discarded). +// - `50 / 100` yields `0` (since 0.5 is less than 1 and thus discarded). +// +// The function does not mutate any arguments and ensures that errors are wrapped with specific messages for clarity. +func (x Dec) QuoInteger(y Dec) (Dec, error) { + var z Dec + _, err := dec128Context.QuoInteger(&z.dec, &x.dec, &y.dec) + if err != nil { + return z, ErrInvalidDec.Wrap(err.Error()) + } + return z, nil +} + +// 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 + if _, err := dec128Context.Mul(&z.dec, &x.dec, &y.dec); err != nil { + return z, ErrInvalidDec.Wrap(err.Error()) + } + return z, nil +} + +// MulExact multiplies two Dec values x and y without rounding, using decimal128 precision. +// It returns an error if rounding is necessary to fit the result within the 34-digit limit. +// +// Example: +// - MulExact(Dec{1.234}, Dec{2.345}) -> Dec{2.893}, or ErrUnexpectedRounding if precision exceeded. +// +// Note: +// - This function does not alter the original Dec values. +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, ErrInvalidDec.Wrap(err.Error()) + } + if condition.Rounded() { + return z, ErrUnexpectedRounding } - // add some extra zero's to correct to the Precision factor - zerosToAdd := LegacyPrecision - lenDecs - zeros := strings.Repeat("0", zerosToAdd) - combinedStr += zeros + return z, nil +} - combined, ok := new(big.Int).SetString(combinedStr, 10) // base 10 +// Modulo computes the remainder of division of x by y using decimal128 precision. +// It returns an error if y is zero or if any other error occurs during the computation. +// +// Example: +// - 7 mod 3 = 1 +// - 6 mod 3 = 0 +func (x Dec) Modulo(y Dec) (Dec, error) { + var z Dec + _, err := dec128Context.Rem(&z.dec, &x.dec, &y.dec) + if err != nil { + return z, ErrInvalidDec.Wrap(err.Error()) + } + return z, errors.Wrap(err, "decimal remainder error") +} + +// 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() +} + +// 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, ok := new(big.Int).SetString(y.Text('f'), 10) if !ok { - return LegacyDec{}, fmt.Errorf("failed to set decimal string with base 10: %s", combinedStr) + return nil, ErrNonIntegral } - 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 + return z, nil } -// 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) -} - -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 -} - -// addition -func (d LegacyDec) Add(d2 LegacyDec) LegacyDec { - return d.ImmutOp(LegacyDec.AddMut, d2) -} - -// 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 -} - -// subtraction -func (d LegacyDec) Sub(d2 LegacyDec) LegacyDec { - return d.ImmutOp(LegacyDec.SubMut, d2) -} - -// 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 -} - -// multiplication -func (d LegacyDec) Mul(d2 LegacyDec) LegacyDec { - return d.ImmutOp(LegacyDec.MulMut, d2) -} - -// 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 -} - -// multiplication truncate -func (d LegacyDec) MulTruncate(d2 LegacyDec) LegacyDec { - return d.ImmutOp(LegacyDec.MulTruncateMut, d2) -} - -// mutable multiplication truncage -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 -} - -// 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.BigInt()) - 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 -} - -// quotient -func (d LegacyDec) Quo(d2 LegacyDec) LegacyDec { - return d.ImmutOp(LegacyDec.QuoMut, d2) -} - -var squaredPrecisionReuse = new(big.Int).Mul(precisionReuse, precisionReuse) - -// 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 -} - -// quotient truncate -func (d LegacyDec) QuoTruncate(d2 LegacyDec) LegacyDec { - return d.ImmutOp(LegacyDec.QuoTruncateMut, d2) -} - -// mutable quotient truncate -func (d LegacyDec) QuoTruncateMut(d2 LegacyDec) LegacyDec { - // multiply precision twice - d.i.Mul(d.i, squaredPrecisionReuse) - d.i.Quo(d.i, d2.i) - - chopPrecisionAndTruncate(d.i) - if d.i.BitLen() > maxDecBitLen { - panic("Int overflow") - } - return d -} - -// quotient, round up -func (d LegacyDec) QuoRoundUp(d2 LegacyDec) LegacyDec { - return d.ImmutOp(LegacyDec.QuoRoundupMut, d2) -} - -// mutable quotient, round up -func (d LegacyDec) QuoRoundupMut(d2 LegacyDec) LegacyDec { - // multiply precision twice - d.i.Mul(d.i, squaredPrecisionReuse) - d.i.Quo(d.i, d2.i) - - chopPrecisionAndRoundUp(d.i) - if d.i.BitLen() > maxDecBitLen { - panic("Int overflow") - } - return d -} - -// 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.BigInt()) - 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() - smallestDec := LegacySmallestDec() - - for iter := 0; delta.AbsMut().GT(smallestDec) && iter < maxApproxRootIterations; iter++ { - // Set prev = guess^{root - 1}, with an optimization for sqrt - // where root=2 => prev = guess. (And thus no extra heap allocations) - prev := guess - if root != 2 { - prev = guess.Power(root - 1) - } - if prev.IsZero() { - prev = smallestDec - } - delta.Set(d).QuoMut(prev) - delta.SubMut(guess) - // delta = delta / root. - // We optimize for sqrt, where root=2 => delta = delta >> 1 - if root == 2 { - delta.i.Rsh(delta.i, 1) +// SdkIntTrim rounds the decimal number towards zero to the nearest integer, then converts and returns it as `sdkmath.Int`. +// It handles both positive and negative values correctly by truncating towards zero. +// This function returns an ErrNonIntegral error if the resulting integer is larger than the maximum value that `sdkmath.Int` can represent. +func (x Dec) SdkIntTrim() (Int, error) { + 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 { - delta.QuoInt64Mut(int64(root)) + decs.Exp(decs, apd.NewBigInt(int64(-y.dec.Exponent)), nil) + r.Quo(&y.dec.Coeff, decs) } - - guess.AddMut(delta) } - - return guess, nil + if x.dec.Negative { + r.Neg(&r) + } + bigInt := r.MathBigInt() + if bigInt.BitLen() > MaxBitLen { + return ZeroInt(), ErrNonIntegral + } + return NewIntFromBigInt(bigInt), 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) +// String formatted in decimal notation: '-ddddd.dddd', no exponent +func (x Dec) String() string { + return string(fmtE(x.dec, 'E')) } -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) -} - -// is integer, e.g. decimals are zero -func (d LegacyDec) IsInteger() bool { - return new(big.Int).Rem(d.i, precisionReuse).Sign() == 0 -} - -// 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. +// Text converts the floating-point number x to a string according +// to the given format. The format is one of: // -// 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 +// 'e' -d.dddde±dd, decimal exponent, exponent digits +// 'E' -d.ddddE±dd, decimal exponent, exponent digits +// 'f' -ddddd.dddd, no exponent +// 'g' like 'e' for large exponents, like 'f' otherwise +// 'G' like 'E' for large exponents, like 'f' otherwise +// +// If format is a different character, Text returns a "%" followed by the +// unrecognized.Format character. The 'f' format has the possibility of +// displaying precision that is not present in the Decimal when it appends +// zeros (the 'g' format avoids the use of 'f' in this case). All other +// formats always show the exact precision of the Decimal. +func (x Dec) Text(format byte) string { + return x.dec.Text(format) +} + +// 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) +} + +// Equal checks if the decimal values of x and y are exactly equal. +// It returns true if both decimals represent the same value, otherwise false. +func (x Dec) Equal(y Dec) bool { + return x.dec.Cmp(&y.dec) == 0 +} + +// IsZero returns true if the decimal is zero. +func (x Dec) IsZero() bool { + return x.dec.IsZero() +} + +// IsNegative returns true if the decimal is negative. +func (x Dec) IsNegative() bool { + return x.dec.Negative && !x.dec.IsZero() +} + +// IsPositive returns true if the decimal is positive. +func (x Dec) IsPositive() bool { + return !x.dec.Negative && !x.dec.IsZero() +} + +// IsFinite returns true if the decimal is finite. +func (x Dec) IsFinite() bool { + return x.dec.Form == apd.Finite +} + +// NumDecimalPlaces returns the number of decimal places in x. +func (x Dec) NumDecimalPlaces() uint32 { + exp := x.dec.Exponent + if exp >= 0 { + return 0 + } + return uint32(-exp) +} + +// Reduce returns a copy of x with all trailing zeros removed and the number of zeros that were removed. +// It does not modify the original decimal. +func (x Dec) Reduce() (Dec, int) { + y := Dec{} + _, n := y.dec.Reduce(&x.dec) + return y, n +} + +// Marshal serializes the decimal value into a byte slice in text format. +// This method represents the decimal in a portable and compact hybrid notation. +// Based on the exponent value, the number is formatted into decimal: -ddddd.ddddd, no exponent +// or scientific notation: -d.ddddE±dd +// +// For example, the following transformations are made: +// - 0 -> 0 +// - 123 -> 123 +// - 10000 -> 10000 +// - -0.001 -> -0.001 +// - -0.000000001 -> -1E-9 +// +// Returns: +// - A byte slice of the decimal in text format. +// - An error if the decimal cannot be reduced or marshaled properly. +func (x Dec) Marshal() ([]byte, error) { + var d apd.Decimal + if _, _, err := dec128Context.Reduce(&d, &x.dec); err != nil { + return nil, ErrInvalidDec.Wrap(err.Error()) + } + return fmtE(d, 'E'), nil +} + +// fmtE formats a decimal number into a byte slice in scientific notation or fixed-point notation depending on the exponent. +// If the adjusted exponent is between -6 and 6 inclusive, it uses fixed-point notation, otherwise it uses scientific notation. +func fmtE(d apd.Decimal, fmt byte) []byte { + var scratch, dest [16]byte + buf := dest[:0] + digits := d.Coeff.Append(scratch[:0], 10) + totalDigits := int64(len(digits)) + adj := int64(d.Exponent) + totalDigits - 1 + if adj > -6 && adj < 6 { + return []byte(d.Text('f')) + } + switch { + case totalDigits > 5: + beforeComma := digits[0 : totalDigits-6] + adj -= int64(len(beforeComma) - 1) + buf = append(buf, beforeComma...) + buf = append(buf, '.') + buf = append(buf, digits[totalDigits-6:]...) + case totalDigits > 1: + buf = append(buf, digits[0]) + buf = append(buf, '.') + buf = append(buf, digits[1:]...) + default: + buf = append(buf, digits[0:]...) } - // 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) + buf = append(buf, fmt) + var ch byte + if adj < 0 { + ch = '-' + adj = -adj + } else { + ch = '+' } + buf = append(buf, ch) + return strconv.AppendInt(buf, adj, 10) } -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 NewIntFromBigInt(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 NewIntFromBigInt(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 interger 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.Cmp(zeroInt) == 0 { - return LegacyNewDecFromBigInt(quo) - } - - if rem.Sign() == -1 { - return LegacyNewDecFromBigInt(quo) - } - - 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()) -} - -// ValidSortableDec 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) -} - -// SortableDecBytes 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) +// Unmarshal parses a byte slice containing a text-formatted decimal and stores the result in the receiver. +// It returns an error if the byte slice does not represent a valid decimal. +func (x *Dec) Unmarshal(data []byte) error { + result, err := NewDecFromString(string(data)) if err != nil { - return err + return ErrInvalidDec.Wrap(err.Error()) } - // TODO: Reuse dec allocation - newDec, err := LegacyNewDecFromStr(text) - if err != nil { - return err + if result.dec.Form != apd.Finite { + return ErrInvalidDec.Wrap("unknown decimal form") } - d.i = newDec.i + x.dec = result.dec 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.Cmp(zeroInt) == 0 { - copy(data, []byte{0x30}) - return 1, nil - } - - bz, err := d.Marshal() +// MarshalTo encodes the receiver into the provided byte slice and returns the number of bytes written and any error encountered. +func (x Dec) MarshalTo(data []byte) (n int, err error) { + bz, err := x.Marshal() if err != nil { return 0, err } - copy(data, bz) - return len(bz), nil + return copy(data, 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() +// Size returns the number of bytes required to encode the Dec value, which is useful for determining storage requirements. +func (x Dec) Size() int { + bz, _ := x.Marshal() return len(bz) } -// 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 +// MarshalJSON serializes the Dec struct into a JSON-encoded byte slice using scientific notation. +func (x Dec) MarshalJSON() ([]byte, error) { + return json.Marshal(fmtE(x.dec, 'E')) } -// minimum decimal between two -func LegacyMinDec(d1, d2 LegacyDec) LegacyDec { - if d1.LT(d2) { - return d1 - } - return d2 -} - -// maximum decimal between two -func LegacyMaxDec(d1, d2 LegacyDec) LegacyDec { - if d1.LT(d2) { - return d2 - } - return d1 -} - -// intended to be used with require/assert: require.True(DecEq(...)) -func LegacyDecEq(t *testing.T, exp, got LegacyDec) (*testing.T, bool, string, string, string) { - 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) { - 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]) +// UnmarshalJSON implements the json.Unmarshaler interface for the Dec type, converting JSON strings to Dec objects. +func (x *Dec) UnmarshalJSON(data []byte) error { + var text string + err := json.Unmarshal(data, &text) if err != nil { - return "", err + return err } - - if len(parts) == 1 { - return intPart, nil + val, err := NewDecFromString(text) + if err != nil { + return err } - - 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 + *x = val + return nil } diff --git a/math/dec_bench_test.go b/math/dec_bench_test.go new file mode 100644 index 0000000000..52d79c8b44 --- /dev/null +++ b/math/dec_bench_test.go @@ -0,0 +1,353 @@ +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 BenchmarkCompareLegacySubAndDecSub(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.Sub(legacyB2) + } + }) + + b.Run("NewDec", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = newB1.Sub(newB2) + } + }) +} diff --git a/math/dec_examples_test.go b/math/dec_examples_test.go new file mode 100644 index 0000000000..c2cf3f5a83 --- /dev/null +++ b/math/dec_examples_test.go @@ -0,0 +1,323 @@ +package math + +import "fmt" + +func ExampleDec() { + d := NewDecFromInt64(1) // 1 + fmt.Println(d.String()) + + d = NewDecWithExp(-1234, -3) // -1.234 + fmt.Println(d.String()) + d = NewDecWithExp(1234, 0) // 1234 + fmt.Println(d.String()) + d = NewDecWithExp(1234, 1) // 12340 + fmt.Println(d.String()) + + // scientific notation + d, err := NewDecFromString("1.23E+4") // 12300 + if err != nil { + panic(err) + } + fmt.Println(d.String()) + + // decimal notation + d, err = NewDecFromString("1.234") + if err != nil { + panic(err) + } + fmt.Println(d.String()) + + // Output: 1 + // -1.234 + // 1234 + // 12340 + // 12300 + // 1.234 +} + +func ExampleDec_Add() { + sum, err := NewDecFromInt64(1).Add(NewDecFromInt64(1)) // 1 + 1 = 2 + if err != nil { + panic(err) + } + fmt.Println(sum.String()) + + const maxExp = 100_000 + _, err = NewDecWithExp(1, maxExp).Add(NewDecFromInt64(1)) // 1E+1000000 + 1 + if err != nil { + fmt.Println(err.Error()) + } + + sum, err = NewDecWithExp(1, maxExp).Add(NewDecWithExp(1, maxExp)) // 1E+1000000 + 1E+1000000 + if err != nil { + panic(err) + } + fmt.Println(sum.Text('E')) + + // the max exponent must not be exceeded + _, err = NewDecWithExp(1, maxExp+1).Add(NewDecFromInt64(1)) // 1E+1000001 + 1 + if err != nil { + fmt.Println(err.Error()) + } + + const minExp = -100_000 + // same for min exponent + _, err = NewDecWithExp(1, minExp-1).Add(NewDecFromInt64(1)) // 1E-1000001 + 1 + if err != nil { + fmt.Println(err.Error()) + } + // not even by adding 0 + _, err = NewDecWithExp(1, minExp-1).Add(NewDecFromInt64(0)) // 1E-1000001 + 0 + if err != nil { + fmt.Println(err.Error()) + } + + // Output: 2 + // 2E+100000 + // add: exponent out of range: invalid decimal + // add: exponent out of range: invalid decimal + // add: exponent out of range: invalid decimal +} + +func ExampleDec_Sub() { + sum, err := NewDecFromInt64(2).Sub(NewDecFromInt64(1)) // 2 - 1 + if err != nil { + panic(err) + } + fmt.Println(sum.String()) + + const maxExp = 100_000 + _, err = NewDecWithExp(1, maxExp).Sub(NewDecFromInt64(1)) // 1E+1000000 - 1 + if err != nil { + fmt.Println(err.Error()) + } + + sum, err = NewDecWithExp(1, maxExp).Sub(NewDecWithExp(1, maxExp)) // 1E+1000000 - 1E+1000000 + if err != nil { + panic(err) + } + fmt.Println(sum.Text('E')) + + // the max exponent must not be exceeded + _, err = NewDecWithExp(1, maxExp+1).Sub(NewDecFromInt64(1)) // 1E+1000001 - 1 + if err != nil { + fmt.Println(err.Error()) + } + + const minExp = -100_000 + // same for min exponent + _, err = NewDecWithExp(1, minExp-1).Sub(NewDecFromInt64(1)) // 1E-1000001 - 1 + if err != nil { + fmt.Println(err.Error()) + } + // not even by adding 0 + _, err = NewDecWithExp(1, minExp-1).Sub(NewDecFromInt64(0)) // 1E-1000001 - 0 + if err != nil { + fmt.Println(err.Error()) + } + + // Output: 1 + // 0E+100000 + // sub: exponent out of range: invalid decimal + // sub: exponent out of range: invalid decimal + // sub: exponent out of range: invalid decimal +} + +func ExampleDec_Quo() { + sum, err := NewDecFromInt64(6).Quo(NewDecFromInt64(2)) // 6 / 2 + if err != nil { + panic(err) + } + fmt.Println(sum.String()) + + sum, err = NewDecFromInt64(7).Quo(NewDecFromInt64(2)) // 7 / 2 + if err != nil { + panic(err) + } + fmt.Println(sum.String()) + + sum, err = NewDecFromInt64(4).Quo(NewDecFromInt64(9)) // 4 / 9 + if err != nil { + panic(err) + } + fmt.Println(sum.String()) + + const minExp = -100_000 + sum, err = NewDecWithExp(1, minExp).Quo(NewDecFromInt64(10)) // 1e-100000 / 10 + if err != nil { + fmt.Println(err.Error()) + } + + sum, err = NewDecFromInt64(1).Quo(NewDecFromInt64(0)) // 1 / 0 -> error + if err != nil { + fmt.Println(err.Error()) + } + + // Output: 3.000000000000000000000000000000000 + // 3.500000000000000000000000000000000 + // 0.4444444444444444444444444444444444 + // exponent out of range: invalid decimal + // division by zero: invalid decimal +} + +func ExampleDec_QuoExact() { + sum, err := NewDecFromInt64(6).QuoExact(NewDecFromInt64(2)) // 6 / 2 + if err != nil { + panic(err) + } + fmt.Println(sum.String()) + + sum, err = NewDecFromInt64(7).QuoExact(NewDecFromInt64(2)) // 7 / 2 + if err != nil { + panic(err) + } + fmt.Println(sum.String()) + + sum, err = NewDecFromInt64(4).QuoExact(NewDecFromInt64(9)) // 4 / 9 -> error + if err != nil { + fmt.Println(err.Error()) + } + + const minExp = -100_000 + sum, err = NewDecWithExp(1, minExp).QuoExact(NewDecFromInt64(10)) // 1e-100000 / 10 -> error + if err != nil { + fmt.Println(err.Error()) + } + + sum, err = NewDecFromInt64(1).QuoExact(NewDecFromInt64(0)) // 1 / 0 -> error + if err != nil { + fmt.Println(err.Error()) + } + + // Output: 3.000000000000000000000000000000000 + // 3.500000000000000000000000000000000 + // unexpected rounding + // exponent out of range: invalid decimal + // division by zero: invalid decimal +} + +func ExampleDec_QuoInteger() { + sum, err := NewDecFromInt64(6).QuoInteger(NewDecFromInt64(2)) // 6 / 2 + if err != nil { + panic(err) + } + fmt.Println(sum.String()) + + sum, err = NewDecFromInt64(7).QuoInteger(NewDecFromInt64(2)) // 7 / 2 + if err != nil { + panic(err) + } + fmt.Println(sum.String()) + + sum, err = NewDecFromInt64(4).QuoInteger(NewDecFromInt64(9)) // 4 / 9 -> error + if err != nil { + panic(err) + } + fmt.Println(sum.String()) + + const minExp = -100_000 + sum, err = NewDecWithExp(1, minExp).QuoInteger(NewDecFromInt64(10)) // 1e-100000 / 10 -> 0 + if err != nil { + panic(err) + } + fmt.Println(sum.String()) + + sum, err = NewDecFromInt64(1).QuoInteger(NewDecFromInt64(0)) // 1 / 0 -> error + if err != nil { + fmt.Println(err.Error()) + } + + // Output: 3 + // 3 + // 0 + // 0 + // division by zero: invalid decimal +} + +func ExampleDec_Mul() { + sum, err := NewDecFromInt64(2).Mul(NewDecFromInt64(3)) // 2 * 3 + if err != nil { + panic(err) + } + fmt.Println(sum.String()) + + sum, err = NewDecWithExp(125, -2).Mul(NewDecFromInt64(2)) // 1.25 * 2 + if err != nil { + panic(err) + } + fmt.Println(sum.String()) + + const maxExp = 100_000 + sum, err = NewDecWithExp(1, maxExp).Mul(NewDecFromInt64(10)) // 1e100000 * 10 -> err + if err != nil { + fmt.Println(err.Error()) + } + + sum, err = NewDecFromInt64(1).Mul(NewDecFromInt64(0)) // 1 * 0 + if err != nil { + panic(err) + } + fmt.Println(sum.String()) + + // Output: 6 + // 2.50 + // exponent out of range: invalid decimal + // 0 +} + +func ExampleDec_MulExact() { + sum, err := NewDecFromInt64(2).MulExact(NewDecFromInt64(3)) // 2 * 3 + if err != nil { + panic(err) + } + fmt.Println(sum.String()) + + sum, err = NewDecWithExp(125, -2).MulExact(NewDecFromInt64(2)) // 1.25 * 2 + if err != nil { + panic(err) + } + fmt.Println(sum.String()) + + const maxExp = 100_000 + sum, err = NewDecWithExp(1, maxExp).MulExact(NewDecFromInt64(10)) // 1e100000 * 10 -> err + if err != nil { + fmt.Println(err.Error()) + } + a, err := NewDecFromString("0.12345678901234567890123456789012345") // 35 digits after the comma + if err != nil { + panic(err) + } + sum, err = a.MulExact(NewDecFromInt64(1)) + if err != nil { + fmt.Println(err.Error()) + } + + sum, err = a.MulExact(NewDecFromInt64(0)) + if err != nil { + panic(err) + } + fmt.Println(sum.String()) + + sum, err = NewDecFromInt64(1).MulExact(NewDecFromInt64(0)) // 1 * 0 + if err != nil { + panic(err) + } + fmt.Println(sum.String()) + + // Output: 6 + // 2.50 + // exponent out of range: invalid decimal + // unexpected rounding + // 0E-35 + // 0 +} + +func ExampleDec_Modulo() { + sum, err := NewDecFromInt64(7).Modulo(NewDecFromInt64(3)) // 7 mod 3 = 1 + if err != nil { + panic(err) + } + fmt.Println(sum.String()) + + // Output: 1 +} diff --git a/math/dec_rapid_test.go b/math/dec_rapid_test.go new file mode 100644 index 0000000000..8a1830b866 --- /dev/null +++ b/math/dec_rapid_test.go @@ -0,0 +1,527 @@ +package math + +import ( + "fmt" + "regexp" + "strconv" + "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)) + + // 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 := NewDecWithExp(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 = 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.Modulo(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: 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 := NewDecWithExp(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 := NewDecWithExp(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 3ff0a9a50e..0b148032eb 100644 --- a/math/dec_test.go +++ b/math/dec_test.go @@ -1,738 +1,1455 @@ -package math_test +package math import ( - "bytes" - "encoding/json" "fmt" - "math/big" - "os" + "math" "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 + 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, 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 math.LegacyDec - expQuo, expQuoRoundUp, expQuoTruncate math.LegacyDec - expAdd, expSub math.LegacyDec - }{ - // d1 d2 MUL MulTruncate 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(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(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(-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(-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(-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(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(0), math.LegacyNewDec(-2)}, - - { - math.LegacyNewDec(3), math.LegacyNewDec(7), 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: NewDecWithExp(1234, -3), }, - { - math.LegacyNewDec(2), math.LegacyNewDec(4), 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: NewDecWithExp(-1234, -3), + }, + "min decimal": { + src: "-" + strings.Repeat("9", 34), + exp: must(NewDecWithExp(-1, 34).Add(NewDecFromInt64(1))), + }, + "max decimal": { + src: strings.Repeat("9", 34), + exp: must(NewDecWithExp(1, 34).Sub(NewDecFromInt64(1))), + }, + "too big": { + src: strings.Repeat("9", 100_0000), + expErr: ErrInvalidDec, + }, + "too small": { + src: "-" + strings.Repeat("9", 100_0000), + expErr: ErrInvalidDec, + }, + "valid decimal with leading zero": { + src: "01234", + exp: NewDecWithExp(1234, 0), + }, + "valid decimal without leading zero": { + src: ".1234", + exp: NewDecWithExp(1234, -4), }, - {math.LegacyNewDec(100), math.LegacyNewDec(100), 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.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(1), math.LegacyNewDec(3), math.LegacyNewDec(0), + "valid decimal without trailing digits": { + src: "123.", + exp: NewDecWithExp(123, 0), }, - { - math.LegacyNewDecWithPrec(3333, 4), math.LegacyNewDecWithPrec(333, 4), 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), + + "valid negative decimal without leading zero": { + src: "-.1234", + exp: NewDecWithExp(-1234, -4), + }, + "valid negative decimal without trailing digits": { + src: "-123.", + exp: NewDecWithExp(-123, 0), + }, + "decimal with scientific notation": { + src: "1.23e4", + exp: NewDecWithExp(123, 2), + }, + "decimal with upper case scientific notation": { + src: "1.23E+4", + exp: NewDecWithExp(123, 2), + }, + "negative decimal with scientific notation": { + src: "-1.23e4", + exp: NewDecWithExp(-123, 2), + }, + "exceed max exp 11E+1000000": { + src: "11E+1000000", + expErr: ErrInvalidDec, + }, + "exceed min exp 11E-1000000": { + src: "11E-1000000", + expErr: ErrInvalidDec, + }, + "exceed max exp 1E100001": { + src: "1E100001", + expErr: ErrInvalidDec, + }, + "exceed min exp 1E-100001": { + src: "1E-100001", + expErr: ErrInvalidDec, + }, + "empty string": { + src: "", + expErr: ErrInvalidDec, + }, + "NaN": { + src: "NaN", + expErr: ErrInvalidDec, + }, + "random string": { + src: "1foo", + expErr: ErrInvalidDec, + }, + "Infinity": { + src: "Infinity", + expErr: ErrInvalidDec, + }, + "Inf": { + src: "Inf", + expErr: ErrInvalidDec, }, } - - 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) - 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) - - 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) 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) 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"), - }, - } - - 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) + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + got, gotErr := NewDecFromString(spec.src) + if spec.expErr != nil { + require.ErrorIs(t, gotErr, spec.expErr, got.String()) + return } - } + require.NoError(t, gotErr) + assert.True(t, spec.exp.Equal(got)) + }) } } -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) +func TestNewDecFromInt64(t *testing.T) { + specs := map[string]struct { + src int64 + exp string + }{ + "zero value": { + src: 0, + exp: "0", + }, + "positive value": { + src: 123, + exp: "123", + }, + "negative value": { + src: -123, + exp: "-123", + }, + "max value": { + src: math.MaxInt64, + exp: "9223372036854.775807E+6", + }, + "min value": { + src: math.MinInt64, + exp: "9223372036854.775808E+6", + }, } - - if sink == nil { - b.Fatal("Benchmark did not run") + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + got := NewDecFromInt64(spec.src) + assert.Equal(t, spec.exp, got.String()) + }) } - sink = (interface{})(nil) } -func BenchmarkLegacyQuoTruncateMut(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.QuoTruncateMut(b2) +func TestAdd(t *testing.T) { + specs := map[string]struct { + x Dec + y Dec + exp Dec + expErr error + }{ + "0 + 0 = 0": { + x: NewDecFromInt64(0), + y: NewDecFromInt64(0), + exp: NewDecFromInt64(0), + }, + "0 + 123 = 123": { + x: NewDecFromInt64(0), + y: NewDecFromInt64(123), + exp: NewDecFromInt64(123), + }, + "0 + -123 = -123": { + x: NewDecFromInt64(0), + y: NewDecFromInt64(-123), + exp: NewDecFromInt64(-123), + }, + "123 + 123 = 246": { + x: NewDecFromInt64(123), + y: NewDecFromInt64(123), + exp: NewDecFromInt64(246), + }, + "-123 + 123 = 0": { + x: NewDecFromInt64(-123), + y: NewDecFromInt64(123), + exp: NewDecFromInt64(0), + }, + "-123 + -123 = -246": { + x: NewDecFromInt64(-123), + y: NewDecFromInt64(-123), + exp: NewDecFromInt64(-246), + }, + "1.234 + 1.234 = 2.468": { + x: NewDecWithExp(1234, -3), + y: NewDecWithExp(1234, -3), + exp: NewDecWithExp(2468, -3), + }, + "1.234 + 123 = 124.234": { + x: NewDecWithExp(1234, -3), + y: NewDecFromInt64(123), + exp: NewDecWithExp(124234, -3), + }, + "1.234 + -123 = -121.766": { + x: NewDecWithExp(1234, -3), + y: NewDecFromInt64(-123), + exp: must(NewDecFromString("-121.766")), + }, + "1.234 + -1.234 = 0": { + x: NewDecWithExp(1234, -3), + y: NewDecWithExp(-1234, -3), + exp: NewDecWithExp(0, -3), + }, + "-1.234 + -1.234 = -2.468": { + x: NewDecWithExp(-1234, -3), + y: NewDecWithExp(-1234, -3), + exp: NewDecWithExp(-2468, -3), + }, + "1e100000 + 9e900000 -> Err": { + x: NewDecWithExp(1, 100_000), + y: NewDecWithExp(9, 900_000), + expErr: ErrInvalidDec, + }, + "1e100000 + -9e900000 -> Err": { + x: NewDecWithExp(1, 100_000), + y: NewDecWithExp(9, 900_000), + expErr: ErrInvalidDec, + }, + "1e100000 + 1e^-1 -> err": { + x: NewDecWithExp(1, 100_000), + y: NewDecWithExp(1, -1), + expErr: ErrInvalidDec, + }, + "1e100000 + -1e^-1 -> err": { + x: NewDecWithExp(1, 100_000), + y: NewDecWithExp(-1, -1), + expErr: ErrInvalidDec, + }, + "1e100000 + 1 -> 100..1": { + x: NewDecWithExp(1, 100_000), + y: NewDecFromInt64(1), + exp: must(NewDecWithExp(1, 100_000).Add(NewDecFromInt64(1))), + }, + "1e100001 + 0 -> err": { + x: NewDecWithExp(1, 100_001), + y: NewDecFromInt64(0), + expErr: ErrInvalidDec, + }, + "-1e100001 + 0 -> err": { + x: NewDecWithExp(1, -100_001), + y: NewDecFromInt64(0), + expErr: ErrInvalidDec, + }, } - - if sink == nil { - b.Fatal("Benchmark did not run") + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + got, gotErr := spec.x.Add(spec.y) + if spec.expErr != nil { + require.ErrorIs(t, gotErr, spec.expErr, got) + return + } + require.NoError(t, gotErr) + assert.Equal(t, spec.exp, got) + }) } - 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() +func TestSub(t *testing.T) { + specs := map[string]struct { + x Dec + y Dec + exp Dec + expErr error + }{ + "0 - 0 = 0": { + x: NewDecFromInt64(0), + y: NewDecFromInt64(0), + exp: NewDecFromInt64(0), + }, + "0 - 123 = -123": { + x: NewDecFromInt64(0), + y: NewDecFromInt64(123), + exp: NewDecFromInt64(-123), + }, + "0 - -123 = 123": { + x: NewDecFromInt64(0), + y: NewDecFromInt64(-123), + exp: NewDecFromInt64(123), + }, + "123 - 123 = 0": { + x: NewDecFromInt64(123), + y: NewDecFromInt64(123), + exp: NewDecFromInt64(0), + }, + "-123 - 123 = -246": { + x: NewDecFromInt64(-123), + y: NewDecFromInt64(123), + exp: NewDecFromInt64(-246), + }, + "-123 - -123 = 0": { + x: NewDecFromInt64(-123), + y: NewDecFromInt64(-123), + exp: NewDecFromInt64(0), + }, + "1.234 - 1.234 = 0.000": { + x: NewDecWithExp(1234, -3), + y: NewDecWithExp(1234, -3), + exp: NewDecWithExp(0, -3), + }, + "1.234 - 123 = -121.766": { + x: NewDecWithExp(1234, -3), + y: NewDecFromInt64(123), + exp: NewDecWithExp(-121766, -3), + }, + "1.234 - -123 = 124.234": { + x: NewDecWithExp(1234, -3), + y: NewDecFromInt64(-123), + exp: NewDecWithExp(124234, -3), + }, + "1.234 - -1.234 = 2.468": { + x: NewDecWithExp(1234, -3), + y: NewDecWithExp(-1234, -3), + exp: NewDecWithExp(2468, -3), + }, + "-1.234 - -1.234 = 2.468": { + x: NewDecWithExp(-1234, -3), + y: NewDecWithExp(-1234, -3), + exp: NewDecWithExp(0, -3), + }, + "1 - 0.999 = 0.001 - rounding after comma": { + x: NewDecFromInt64(1), + y: NewDecWithExp(999, -3), + exp: NewDecWithExp(1, -3), + }, + "1e100000 - 1^-1 -> Err": { + x: NewDecWithExp(1, 100_000), + y: NewDecWithExp(1, -1), + expErr: ErrInvalidDec, + }, + "1e100000 - 1^1-> Err": { + x: NewDecWithExp(1, 100_000), + y: NewDecWithExp(1, -1), + expErr: ErrInvalidDec, + }, + "upper exp limit exceeded": { + x: NewDecWithExp(1, 100_001), + y: NewDecWithExp(1, 100_001), + expErr: ErrInvalidDec, + }, + "lower exp limit exceeded": { + x: NewDecWithExp(1, -100_001), + y: NewDecWithExp(1, -100_001), + expErr: ErrInvalidDec, + }, + "1e100000 - 1 = 999..9": { + x: NewDecWithExp(1, 100_000), + y: NewDecFromInt64(1), + exp: must(NewDecFromString(strings.Repeat("9", 100_000))), + }, + "1e100000 - 0 = 1e100000": { + x: NewDecWithExp(1, 100_000), + y: NewDecFromInt64(0), + exp: must(NewDecFromString("1e100000")), + }, + "1e100001 - 0 -> err": { + x: NewDecWithExp(1, 100_001), + y: NewDecFromInt64(0), + expErr: ErrInvalidDec, + }, + "1e100000 - -1 -> 100..1": { + x: NewDecWithExp(1, 100_000), + y: must(NewDecFromString("-9e100000")), + expErr: ErrInvalidDec, + }, + "1e-100000 - 0 = 1e-100000": { + x: NewDecWithExp(1, -100_000), + y: NewDecFromInt64(0), + exp: must(NewDecFromString("1e-100000")), + }, + "1e-100001 - 0 -> err": { + x: NewDecWithExp(1, -100_001), + y: NewDecFromInt64(0), + expErr: ErrInvalidDec, + }, + "1e-100000 - -1 -> 0.000..01": { + x: NewDecWithExp(1, -100_000), + y: NewDecFromInt64(-1), + exp: must(NewDecFromString("1." + strings.Repeat("0", 99999) + "1")), + }, } - - if sink == nil { - b.Fatal("Benchmark did not run") + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + got, gotErr := spec.x.Sub(spec.y) + if spec.expErr != nil { + require.ErrorIs(t, gotErr, spec.expErr) + return + } + require.NoError(t, gotErr) + assert.True(t, spec.exp.Equal(got), got.String()) + }) } - sink = (interface{})(nil) } -func BenchmarkLegacyQuoRoundupMut(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.QuoRoundupMut(b2) +func TestQuo(t *testing.T) { + specs := map[string]struct { + src string + x Dec + y Dec + exp Dec + expErr error + }{ + "0 / 0 -> Err": { + x: NewDecFromInt64(0), + y: NewDecFromInt64(0), + expErr: ErrInvalidDec, + }, + " 0 / 123 = 0": { + x: NewDecFromInt64(0), + y: NewDecFromInt64(123), + exp: NewDecFromInt64(0), + }, + "123 / 0 = 0": { + x: NewDecFromInt64(123), + y: NewDecFromInt64(0), + expErr: ErrInvalidDec, + }, + "-123 / 0 = 0": { + x: NewDecFromInt64(-123), + y: NewDecFromInt64(0), + expErr: ErrInvalidDec, + }, + "123 / 123 = 1": { + x: NewDecFromInt64(123), + y: NewDecFromInt64(123), + exp: must(NewDecFromString("1.000000000000000000000000000000000")), + }, + "-123 / 123 = -1": { + x: NewDecFromInt64(-123), + y: NewDecFromInt64(123), + exp: must(NewDecFromString("-1.000000000000000000000000000000000")), + }, + "-123 / -123 = 1": { + x: NewDecFromInt64(-123), + y: NewDecFromInt64(-123), + exp: must(NewDecFromString("1.000000000000000000000000000000000")), + }, + "1.234 / 1.234 = 1": { + x: NewDecWithExp(1234, -3), + y: NewDecWithExp(1234, -3), + exp: must(NewDecFromString("1.000000000000000000000000000000000")), + }, + "-1.234 / 1234 = -1": { + x: NewDecWithExp(-1234, -3), + y: NewDecWithExp(1234, -3), + exp: must(NewDecFromString("-1.000000000000000000000000000000000")), + }, + "1.234 / -123 = 1.0100": { + x: NewDecWithExp(1234, -3), + y: NewDecFromInt64(-123), + exp: must(NewDecFromString("-0.01003252032520325203252032520325203")), + }, + "1.234 / -1.234 = -1": { + x: NewDecWithExp(1234, -3), + y: NewDecWithExp(-1234, -3), + exp: must(NewDecFromString("-1.000000000000000000000000000000000")), + }, + "-1.234 / -1.234 = 1": { + x: NewDecWithExp(-1234, -3), + y: NewDecWithExp(-1234, -3), + exp: must(NewDecFromString("1.000000000000000000000000000000000")), + }, + "3 / -9 = -0.3333...3 - round down": { + x: NewDecFromInt64(3), + y: NewDecFromInt64(-9), + exp: must(NewDecFromString("-0.3333333333333333333333333333333333")), + }, + "4 / 9 = 0.4444...4 - round down": { + x: NewDecFromInt64(4), + y: NewDecFromInt64(9), + exp: must(NewDecFromString("0.4444444444444444444444444444444444")), + }, + "5 / 9 = 0.5555...6 - round up": { + x: NewDecFromInt64(5), + y: NewDecFromInt64(9), + exp: must(NewDecFromString("0.5555555555555555555555555555555556")), + }, + "6 / 9 = 0.6666...7 - round up": { + x: NewDecFromInt64(6), + y: NewDecFromInt64(9), + exp: must(NewDecFromString("0.6666666666666666666666666666666667")), + }, + "7 / 9 = 0.7777...8 - round up": { + x: NewDecFromInt64(7), + y: NewDecFromInt64(9), + exp: must(NewDecFromString("0.7777777777777777777777777777777778")), + }, + "8 / 9 = 0.8888...9 - round up": { + x: NewDecFromInt64(8), + y: NewDecFromInt64(9), + exp: must(NewDecFromString("0.8888888888888888888888888888888889")), + }, + "9e-34 / 10 = 9e-35 - no rounding": { + x: NewDecWithExp(9, -34), + y: NewDecFromInt64(10), + exp: must(NewDecFromString("9e-35")), + }, + "9e-35 / 10 = 9e-36 - no rounding": { + x: NewDecWithExp(9, -35), + y: NewDecFromInt64(10), + exp: must(NewDecFromString("9e-36")), + }, + "high precision - min/0.1": { + x: NewDecWithExp(1, -100_000), + y: NewDecWithExp(1, -1), + exp: NewDecWithExp(1, -99_999), + }, + "high precision - min/1": { + x: NewDecWithExp(1, -100_000), + y: NewDecWithExp(1, 0), + exp: NewDecWithExp(1, -100_000), + }, + "high precision - min/10": { + x: NewDecWithExp(1, -100_000), + y: NewDecWithExp(1, 1), + expErr: ErrInvalidDec, + }, + "high precision - <_min/0.1": { + x: NewDecWithExp(1, -100_001), + y: NewDecWithExp(1, -1), + exp: NewDecWithExp(1, -100_000), + }, + "high precision - <_min/1": { + x: NewDecWithExp(1, -100_001), + y: NewDecWithExp(1, 0), + expErr: ErrInvalidDec, + }, + "high precision - <_min/10": { + x: NewDecWithExp(1, -100_001), + y: NewDecWithExp(1, 1), + expErr: ErrInvalidDec, + }, + "high precision - min/-0.1": { + x: NewDecWithExp(1, -100_000), + y: NewDecWithExp(-1, -1), + exp: NewDecWithExp(-1, -99_999), + }, + "high precision - min/-1": { + x: NewDecWithExp(1, -100_000), + y: NewDecWithExp(-1, 0), + exp: NewDecWithExp(-1, -100_000), + }, + "high precision - min/-10": { + x: NewDecWithExp(1, -100_000), + y: NewDecWithExp(-1, 1), + expErr: ErrInvalidDec, + }, + "high precision - <_min/-0.1": { + x: NewDecWithExp(1, -100_001), + y: NewDecWithExp(-1, -1), + exp: NewDecWithExp(-1, -100_000), + }, + "high precision - <_min/-1": { + x: NewDecWithExp(1, -100_001), + y: NewDecWithExp(-1, 0), + expErr: ErrInvalidDec, + }, + "high precision - <_min/-10": { + x: NewDecWithExp(1, -100_001), + y: NewDecWithExp(-1, 1), + expErr: ErrInvalidDec, + }, } - - if sink == nil { - b.Fatal("Benchmark did not run") + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + got, gotErr := spec.x.Quo(spec.y) + if spec.expErr != nil { + require.ErrorIs(t, gotErr, spec.expErr) + return + } + require.NoError(t, gotErr) + last35 := func(s string) string { + var x int + if len(s) < 36 { + x = 0 + } else { + x = len(s) - 36 + } + return fmt.Sprintf("%s(%d)", s[x:], len(s)) + } + gotReduced, _ := got.Reduce() + assert.True(t, spec.exp.Equal(gotReduced), "exp %s, got: %s", last35(spec.exp.String()), last35(gotReduced.String())) + }) } - 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) +func TestQuoExact(t *testing.T) { + specs := map[string]struct { + src string + x Dec + y Dec + exp Dec + expErr error + }{ + "0 / 0 -> Err": { + x: NewDecFromInt64(0), + y: NewDecFromInt64(0), + expErr: ErrInvalidDec, + }, + " 0 / 123 = 0": { + x: NewDecFromInt64(0), + y: NewDecFromInt64(123), + exp: NewDecFromInt64(0), + }, + "123 / 0 -> Err": { + x: NewDecFromInt64(123), + y: NewDecFromInt64(0), + expErr: ErrInvalidDec, + }, + "-123 / 0 -> Err": { + x: NewDecFromInt64(-123), + y: NewDecFromInt64(0), + expErr: ErrInvalidDec, + }, + "123 / 123 = 1": { + x: NewDecFromInt64(123), + y: NewDecFromInt64(123), + exp: must(NewDecFromString("1.000000000000000000000000000000000")), + }, + "-123 / 123 = 1": { + x: NewDecFromInt64(-123), + y: NewDecFromInt64(123), + exp: must(NewDecFromString("-1.000000000000000000000000000000000")), + }, + "-123 / -123 = 1": { + x: NewDecFromInt64(-123), + y: NewDecFromInt64(-123), + exp: must(NewDecFromString("1.000000000000000000000000000000000")), + }, + "1.234 / 1.234 = 1": { + x: NewDecWithExp(1234, -3), + y: NewDecWithExp(1234, -3), + exp: must(NewDecFromString("1.000000000000000000000000000000000")), + }, + "-1.234 / 1.234 = -1": { + x: NewDecWithExp(-1234, -3), + y: NewDecWithExp(1234, -3), + exp: must(NewDecFromString("-1.000000000000000000000000000000000")), + }, + "1.234 / -123 -> Err": { + x: NewDecWithExp(1234, -3), + y: NewDecFromInt64(-123), + expErr: ErrUnexpectedRounding, + }, + "1.234 / -1.234 = -1": { + x: NewDecWithExp(1234, -3), + y: NewDecWithExp(-1234, -3), + exp: must(NewDecFromString("-1.000000000000000000000000000000000")), + }, + "-1.234 / -1.234 = 1": { + x: NewDecWithExp(-1234, -3), + y: NewDecWithExp(-1234, -3), + exp: must(NewDecFromString("1.000000000000000000000000000000000")), + }, + "3 / -9 -> Err": { + x: NewDecFromInt64(3), + y: NewDecFromInt64(-9), + expErr: ErrUnexpectedRounding, + }, + "4 / 9 -> Err": { + x: NewDecFromInt64(4), + y: NewDecFromInt64(9), + expErr: ErrUnexpectedRounding, + }, + "5 / 9 -> Err": { + x: NewDecFromInt64(5), + y: NewDecFromInt64(9), + expErr: ErrUnexpectedRounding, + }, + "6 / 9 -> Err": { + x: NewDecFromInt64(6), + y: NewDecFromInt64(9), + expErr: ErrUnexpectedRounding, + }, + "7 / 9 -> Err": { + x: NewDecFromInt64(7), + y: NewDecFromInt64(9), + expErr: ErrUnexpectedRounding, + }, + "8 / 9 -> Err": { + x: NewDecFromInt64(8), + y: NewDecFromInt64(9), + expErr: ErrUnexpectedRounding, + }, + "9e-34 / 10 = 9e-35 - no rounding": { + x: NewDecWithExp(9, -34), + y: NewDecFromInt64(10), + exp: must(NewDecFromString("0.00000000000000000000000000000000009000000000000000000000000000000000")), + }, + "9e-35 / 10 = 9e-36 - no rounding": { + x: NewDecWithExp(9, -35), + y: NewDecFromInt64(10), + exp: must(NewDecFromString("9e-36")), + }, + "high precision - min/0.1": { + x: NewDecWithExp(1, -100_000), + y: NewDecWithExp(1, -1), + exp: NewDecWithExp(1, -99_999), + }, + "high precision - min/1": { + x: NewDecWithExp(1, -100_000), + y: NewDecWithExp(1, 0), + exp: NewDecWithExp(1, -100_000), + }, + "high precision - min/10": { + x: NewDecWithExp(1, -100_000), + y: NewDecWithExp(1, 1), + expErr: ErrInvalidDec, + }, + "high precision - <_min/0.1": { + x: NewDecWithExp(1, -100_001), + y: NewDecWithExp(1, -1), + exp: NewDecWithExp(1, -100_000), + }, + "high precision - <_min/1": { + x: NewDecWithExp(1, -100_001), + y: NewDecWithExp(1, 0), + expErr: ErrInvalidDec, + }, + "high precision - <_min/10 -> Err": { + x: NewDecWithExp(1, -100_001), + y: NewDecWithExp(1, 1), + expErr: ErrInvalidDec, + }, + "high precision - min/-0.1": { + x: NewDecWithExp(1, -100_000), + y: NewDecWithExp(-1, -1), + exp: NewDecWithExp(-1, -99_999), + }, + "high precision - min/-1": { + x: NewDecWithExp(1, -100_000), + y: NewDecWithExp(-1, 0), + exp: NewDecWithExp(-1, -100_000), + }, + "high precision - min/-10 -> Err": { + x: NewDecWithExp(1, -100_000), + y: NewDecWithExp(-1, 1), + expErr: ErrInvalidDec, + }, + "high precision - <_min/-0.1": { + x: NewDecWithExp(1, -100_001), + y: NewDecWithExp(-1, -1), + exp: NewDecWithExp(-1, -100_000), + }, + "high precision - <_min/-1": { + x: NewDecWithExp(1, -100_001), + y: NewDecWithExp(-1, 0), + expErr: ErrInvalidDec, + }, + "high precision - <_min/-10": { + x: NewDecWithExp(1, -100_001), + y: NewDecWithExp(-1, 1), + expErr: ErrInvalidDec, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + got, gotErr := spec.x.QuoExact(spec.y) - for _, tc := range testcases { - tc := tc - t.Run(tc[0], func(t *testing.T) { - out, err := math.FormatDec(tc[0]) + if spec.expErr != nil { + require.ErrorIs(t, gotErr, spec.expErr) + return + } + require.NoError(t, gotErr) + assert.True(t, spec.exp.Equal(got)) + }) + } +} + +func TestQuoInteger(t *testing.T) { + specs := map[string]struct { + src string + x Dec + y Dec + exp Dec + expErr error + }{ + "0 / 0 -> Err": { + x: NewDecFromInt64(0), + y: NewDecFromInt64(0), + expErr: ErrInvalidDec, + }, + " 0 / 123 = 0": { + x: NewDecFromInt64(0), + y: NewDecFromInt64(123), + exp: NewDecFromInt64(0), + }, + "123 / 0 -> Err": { + x: NewDecFromInt64(123), + y: NewDecFromInt64(0), + expErr: ErrInvalidDec, + }, + "-123 / -> Err": { + x: NewDecFromInt64(-123), + y: NewDecFromInt64(0), + expErr: ErrInvalidDec, + }, + "123 / 123 = 1": { + x: NewDecFromInt64(123), + y: NewDecFromInt64(123), + exp: NewDecFromInt64(1), + }, + "-123 / 123 = -1": { + x: NewDecFromInt64(-123), + y: NewDecFromInt64(123), + exp: NewDecFromInt64(-1), + }, + "-123 / -123 = 1": { + x: NewDecFromInt64(-123), + y: NewDecFromInt64(-123), + exp: NewDecFromInt64(1), + }, + "1.234 / 1.234": { + x: NewDecWithExp(1234, -3), + y: NewDecWithExp(1234, -3), + exp: NewDecFromInt64(1), + }, + "-1.234 / 1234 = -121.766": { + x: NewDecWithExp(-1234, -3), + y: NewDecWithExp(1234, -3), + exp: NewDecFromInt64(-1), + }, + "1.234 / -1.234 = 2.468": { + x: NewDecWithExp(1234, -3), + y: NewDecWithExp(-1234, -3), + exp: NewDecFromInt64(-1), + }, + "-1.234 / -1.234 = 1": { + x: NewDecWithExp(-1234, -3), + y: NewDecWithExp(-1234, -3), + exp: NewDecFromInt64(1), + }, + "3 / -9 = 0": { + x: NewDecFromInt64(3), + y: NewDecFromInt64(-9), + exp: must(NewDecFromString("0")), + }, + "8 / 9 = 0": { + x: NewDecFromInt64(8), + y: NewDecFromInt64(9), + exp: must(NewDecFromString("0")), + }, + "high precision - min/0.1": { + x: NewDecWithExp(1, -100_000), + y: NewDecWithExp(1, -1), + exp: NewDecFromInt64(0), + }, + "high precision - <_min/-1 -> Err": { + x: NewDecWithExp(1, -100_001), + y: NewDecWithExp(-1, 0), + expErr: ErrInvalidDec, + }, + "high precision - <_min/-10 -> Err": { + x: NewDecWithExp(1, -100_001), + y: NewDecWithExp(-1, 1), + expErr: ErrInvalidDec, + }, + "1e000 / 1 -> Err": { + x: NewDecWithExp(1, 100_000), + y: NewDecFromInt64(1), + expErr: ErrInvalidDec, + }, + "1e100000 - 1^1 -> Err": { + x: NewDecWithExp(1, 100_000), + y: NewDecWithExp(1, -1), + expErr: ErrInvalidDec, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + got, gotErr := spec.x.QuoInteger(spec.y) + + if spec.expErr != nil { + require.ErrorIs(t, gotErr, spec.expErr) + return + } + require.NoError(t, gotErr) + assert.True(t, spec.exp.Equal(got)) + }) + } +} + +func TestModulo(t *testing.T) { + specs := map[string]struct { + x Dec + y Dec + exp Dec + expErr error + }{ + "0 / 123 = 0": { + x: NewDecFromInt64(0), + y: NewDecFromInt64(123), + exp: NewDecFromInt64(0), + }, + "123 / 10 = 3": { + x: NewDecFromInt64(123), + y: NewDecFromInt64(10), + exp: NewDecFromInt64(3), + }, + "123 / -10 = 3": { + x: NewDecFromInt64(123), + y: NewDecFromInt64(-10), + exp: NewDecFromInt64(3), + }, + "-123 / 10 = -3": { + x: NewDecFromInt64(-123), + y: NewDecFromInt64(10), + exp: NewDecFromInt64(-3), + }, + "1.234 / 1 = 0.234": { + x: NewDecWithExp(1234, -3), + y: NewDecFromInt64(1), + exp: NewDecWithExp(234, -3), + }, + "1.234 / 0.1 = 0.034": { + x: NewDecWithExp(1234, -3), + y: NewDecWithExp(1, -1), + exp: NewDecWithExp(34, -3), + }, + "1.234 / 1.1 = 0.134": { + x: NewDecWithExp(1234, -3), + y: NewDecWithExp(11, -1), + exp: NewDecWithExp(134, -3), + }, + "10 / 0 -> Err": { + x: NewDecFromInt64(10), + y: NewDecFromInt64(0), + expErr: ErrInvalidDec, + }, + "-1e0000 / 9e0000 = 1e0000": { + x: NewDecWithExp(-1, 100_000), + y: NewDecWithExp(9, 100_000), + exp: NewDecWithExp(-1, 100_000), + }, + "1e0000 / 9e0000 = 1e0000": { + x: NewDecWithExp(1, 100_000), + y: NewDecWithExp(9, 100_000), + exp: NewDecWithExp(1, 100_000), + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + got, gotErr := spec.x.Modulo(spec.y) + + if spec.expErr != nil { + require.ErrorIs(t, gotErr, spec.expErr) + return + } + require.NoError(t, gotErr) + assert.True(t, spec.exp.Equal(got)) + }) + } +} + +func TestNumDecimalPlaces(t *testing.T) { + specs := map[string]struct { + src Dec + exp uint32 + }{ + "integer": { + src: NewDecFromInt64(123), + exp: 0, + }, + "one decimal place": { + src: NewDecWithExp(1234, -1), + exp: 1, + }, + "two decimal places": { + src: NewDecWithExp(12345, -2), + exp: 2, + }, + "three decimal places": { + src: NewDecWithExp(123456, -3), + exp: 3, + }, + "trailing zeros": { + src: NewDecWithExp(123400, -4), + exp: 4, + }, + "zero value": { + src: NewDecFromInt64(0), + exp: 0, + }, + "negative value": { + src: NewDecWithExp(-12345, -3), + exp: 3, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + got := spec.src.NumDecimalPlaces() + assert.Equal(t, spec.exp, got) + }) + } +} + +func TestCmp(t *testing.T) { + specs := map[string]struct { + x Dec + y Dec + exp int + }{ + "0 == 0": { + x: NewDecFromInt64(0), + y: NewDecFromInt64(0), + exp: 0, + }, + "0 < 123 = -1": { + x: NewDecFromInt64(0), + y: NewDecFromInt64(123), + exp: -1, + }, + "123 > 0 = 1": { + x: NewDecFromInt64(123), + y: NewDecFromInt64(0), + exp: 1, + }, + "-123 < 0 = -1": { + x: NewDecFromInt64(-123), + y: NewDecFromInt64(0), + exp: -1, + }, + "123 == 123": { + x: NewDecFromInt64(123), + y: NewDecFromInt64(123), + exp: 0, + }, + "-123 == -123": { + x: NewDecFromInt64(-123), + y: NewDecFromInt64(-123), + exp: 0, + }, + "1.234 == 1.234": { + x: NewDecWithExp(1234, -3), + y: NewDecWithExp(1234, -3), + exp: 0, + }, + "1.234 > 1.233": { + x: NewDecWithExp(1234, -3), + y: NewDecWithExp(1233, -3), + exp: 1, + }, + "1.233 < 1.234": { + x: NewDecWithExp(1233, -3), + y: NewDecWithExp(1234, -3), + exp: -1, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + got := spec.x.Cmp(spec.y) + assert.Equal(t, spec.exp, got) + }) + } +} + +func TestReduce(t *testing.T) { + specs := map[string]struct { + src string + exp string + decPlaces int + }{ + "positive value": { + src: "10", + exp: "10", + decPlaces: 1, + }, + "negative value": { + src: "-10", + exp: "-10", + decPlaces: 1, + }, + "positive decimal": { + src: "1.30000", + exp: "1.3", + decPlaces: 4, + }, + "negative decimal": { + src: "-1.30000", + exp: "-1.3", + decPlaces: 4, + }, + "zero decimal and decimal places": { + src: "0.00000", + exp: "0", + decPlaces: 0, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + src := must(NewDecFromString(spec.src)) + got, gotZerosRemoved := src.Reduce() + assert.Equal(t, spec.decPlaces, gotZerosRemoved) + assert.Equal(t, spec.exp, got.String()) + }) + } +} + +func TestMulExact(t *testing.T) { + specs := map[string]struct { + x Dec + y Dec + exp Dec + expErr error + }{ + "200 * 200 = 40000": { + x: NewDecFromInt64(200), + y: NewDecFromInt64(200), + exp: NewDecFromInt64(40000), + }, + "-200 * -200 = 40000": { + x: NewDecFromInt64(-200), + y: NewDecFromInt64(-200), + exp: NewDecFromInt64(40000), + }, + "-100 * -100 = 10000": { + x: NewDecFromInt64(-100), + y: NewDecFromInt64(-100), + exp: NewDecFromInt64(10000), + }, + "0 * 0 = 0": { + x: NewDecFromInt64(0), + y: NewDecFromInt64(0), + exp: NewDecFromInt64(0), + }, + "1.1 * 1.1 = 1.21": { + x: NewDecWithExp(11, -1), + y: NewDecWithExp(11, -1), + exp: NewDecWithExp(121, -2), + }, + "1.000 * 1.000 = 1.000000": { + x: NewDecWithExp(1000, -3), + y: NewDecWithExp(1000, -3), + exp: must(NewDecFromString("1.000000")), + }, + "0.0000001 * 0.0000001 = 0": { + x: NewDecWithExp(0o0000001, -7), + y: NewDecWithExp(0o0000001, -7), + exp: NewDecWithExp(1, -14), + }, + "0.12345678901234567890123456789012345 * 1": { + x: must(NewDecFromString("0.12345678901234567890123456789012345")), + y: NewDecWithExp(1, 0), + expErr: ErrUnexpectedRounding, + }, + "0.12345678901234567890123456789012345 * 0": { + x: must(NewDecFromString("0.12345678901234567890123456789012345")), + y: NewDecFromInt64(0), + exp: NewDecFromInt64(0), + }, + "0.12345678901234567890123456789012345 * 0.1": { + x: must(NewDecFromString("0.12345678901234567890123456789012345")), + y: NewDecWithExp(1, -1), + expErr: ErrUnexpectedRounding, + }, + "1000001 * 1.000001 = 1000002.000001": { + x: NewDecFromInt64(1000001), + y: NewDecWithExp(1000001, -6), + exp: must(NewDecFromString("1000002.000001")), + }, + "1000001 * 1000000 = 1000001000000 ": { + x: NewDecFromInt64(1000001), + y: NewDecFromInt64(1000000), + exp: NewDecFromInt64(1000001000000), + }, + "1e0000 * 1e0000 -> Err": { + x: NewDecWithExp(1, 100_000), + y: NewDecWithExp(1, 100_000), + expErr: ErrInvalidDec, + }, + "1e0000 * 1 = 1e0000": { + x: NewDecWithExp(1, 100_000), + y: NewDecWithExp(1, 0), + exp: NewDecWithExp(1, 100_000), + }, + "1e100000 * 9 = 9e100000": { + x: NewDecWithExp(1, 100_000), + y: NewDecFromInt64(9), + exp: NewDecWithExp(9, 100_000), + }, + "1e100000 * 10 = err": { + x: NewDecWithExp(1, 100_000), + y: NewDecWithExp(1, 1), + expErr: ErrInvalidDec, + }, + "1e0000 * -1 = -1e0000": { + x: NewDecWithExp(1, 100_000), + y: NewDecWithExp(-1, 0), + exp: NewDecWithExp(-1, 100_000), + }, + "1e100000 * -9 = 9e100000": { + x: NewDecWithExp(1, 100_000), + y: NewDecFromInt64(-9), + exp: NewDecWithExp(-9, 100_000), + }, + "1e100000 * -10 = err": { + x: NewDecWithExp(1, 100_000), + y: NewDecWithExp(-1, 1), + expErr: ErrInvalidDec, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + got, gotErr := spec.x.MulExact(spec.y) + if spec.expErr != nil { + require.ErrorIs(t, gotErr, spec.expErr, gotErr) + return + } + require.NoError(t, gotErr) + assert.True(t, spec.exp.Equal(got), "exp: %s, got: %s", spec.exp.Text('E'), got.Text('E')) + }) + } +} + +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", "", ErrNonIntegral}, + } + for idx, tc := range tcs { + t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) { + a, err := NewDecFromString(tc.intStr) 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) + 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 TestNegativePrecisionPanic(t *testing.T) { - require.Panics(t, func() { - math.LegacyNewDecWithPrec(10, -1) - }) +func TestToSdkInt(t *testing.T) { + maxIntValue := "115792089237316195423570985008687907853269984665640564039457584007913129639935" // 2^256 -1 + tcs := []struct { + src string + exp string + expErr bool + }{ + {src: maxIntValue, exp: maxIntValue}, + {src: "1000000000000000000000000000000000000123456789.00000001", exp: "1000000000000000000000000000000000000123456789"}, + {src: "123.456e6", exp: "123456000"}, + {src: "123.456e1", exp: "1234"}, + {src: "123.456", exp: "123"}, + {src: "123.956", exp: "123"}, + {src: "-123.456", exp: "-123"}, + {src: "-123.956", exp: "-123"}, + {src: "-0.956", exp: "0"}, + {src: "-0.9", exp: "0"}, + {src: "1E-100000", exp: "0"}, + {src: "115792089237316195423570985008687907853269984665640564039457584007913129639936", expErr: true}, // 2^256 + {src: "1E100000", expErr: true}, + } + for _, tc := range tcs { + t.Run(fmt.Sprint(tc.src), func(t *testing.T) { + a, err := NewDecFromString(tc.src) + require.NoError(t, err) + b, gotErr := a.SdkIntTrim() + if tc.expErr { + require.Error(t, gotErr, "value: %s", b.String()) + return + } + require.NoError(t, gotErr) + require.Equal(t, tc.exp, b.String()) + }) + } +} + +func TestInfDecString(t *testing.T) { + _, err := NewDecFromString("iNf") + require.Error(t, err) + require.ErrorIs(t, err, ErrInvalidDec) +} + +func must[T any](r T, err error) T { + if err != nil { + panic(err) + } + return r +} + +func TestMarshalUnmarshal(t *testing.T) { + specs := map[string]struct { + x Dec + exp string + expErr error + }{ + "Zero value": { + x: NewDecFromInt64(0), + exp: "0", + }, + "-0": { + x: NewDecFromInt64(-0), + exp: "0", + }, + "1 decimal place": { + x: must(NewDecFromString("0.1")), + exp: "0.1", + }, + "2 decimal places": { + x: must(NewDecFromString("0.01")), + exp: "0.01", + }, + "3 decimal places": { + x: must(NewDecFromString("0.001")), + exp: "0.001", + }, + "4 decimal places": { + x: must(NewDecFromString("0.0001")), + exp: "0.0001", + }, + "5 decimal places": { + x: must(NewDecFromString("0.00001")), + exp: "0.00001", + }, + "6 decimal places": { + x: must(NewDecFromString("0.000001")), + exp: "1E-6", + }, + "7 decimal places": { + x: must(NewDecFromString("0.0000001")), + exp: "1E-7", + }, + "1": { + x: must(NewDecFromString("1")), + exp: "1", + }, + "12": { + x: must(NewDecFromString("12")), + exp: "12", + }, + "123": { + x: must(NewDecFromString("123")), + exp: "123", + }, + "1234": { + x: must(NewDecFromString("1234")), + exp: "1234", + }, + "12345": { + x: must(NewDecFromString("12345")), + exp: "12345", + }, + "123456": { + x: must(NewDecFromString("123456")), + exp: "123456", + }, + "1234567": { + x: must(NewDecFromString("1234567")), + exp: "1.234567E+6", + }, + "12345678": { + x: must(NewDecFromString("12345678")), + exp: "12.345678E+6", + }, + "123456789": { + x: must(NewDecFromString("123456789")), + exp: "123.456789E+6", + }, + "1234567890": { + x: must(NewDecFromString("1234567890")), + exp: "123.456789E+7", + }, + "12345678900": { + x: must(NewDecFromString("12345678900")), + exp: "123.456789E+8", + }, + "negative 1 with negative exponent": { + x: must(NewDecFromString("-1.000001")), + exp: "-1.000001", + }, + "-1.0000001 - negative 1 with negative exponent": { + x: must(NewDecFromString("-1.0000001")), + exp: "-1.0000001", + }, + "3 decimal places before the comma": { + x: must(NewDecFromString("100")), + exp: "100", + }, + "4 decimal places before the comma": { + x: must(NewDecFromString("1000")), + exp: "1000", + }, + "5 decimal places before the comma": { + x: must(NewDecFromString("10000")), + exp: "10000", + }, + "6 decimal places before the comma": { + x: must(NewDecFromString("100000")), + exp: "100000", + }, + "7 decimal places before the comma": { + x: must(NewDecFromString("1000000")), + exp: "1E+6", + }, + "1e100000": { + x: NewDecWithExp(1, 100_000), + exp: "1E+100000", + }, + "1.1e100000": { + x: must(NewDecFromString("1.1e100000")), + exp: "1.1E+100000", + }, + "1e100001": { + x: NewDecWithExp(1, 100_001), + expErr: ErrInvalidDec, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + marshaled, gotErr := spec.x.Marshal() + if spec.expErr != nil { + require.ErrorIs(t, gotErr, spec.expErr) + return + } + require.NoError(t, gotErr) + assert.Equal(t, spec.exp, string(marshaled)) + // and backwards + unmarshalledDec := new(Dec) + require.NoError(t, unmarshalledDec.Unmarshal(marshaled)) + assert.Equal(t, spec.exp, unmarshalledDec.String()) + assert.True(t, spec.x.Equal(*unmarshalledDec)) + }) + } } diff --git a/math/go.mod b/math/go.mod index 03e1c2a1bc..8259b48f6e 100644 --- a/math/go.mod +++ b/math/go.mod @@ -1,17 +1,39 @@ module cosmossdk.io/math -go 1.23 +go 1.22 require ( - github.com/stretchr/testify v1.8.4 - sigs.k8s.io/yaml v1.3.0 + github.com/cockroachdb/apd/v3 v3.2.1 + github.com/stretchr/testify v1.10.0 + 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 - gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sys v0.29.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250122153221-138b5a5a4fd4 // indirect + google.golang.org/grpc v1.70.0 // indirect + google.golang.org/protobuf v1.36.4 // indirect +) + +require ( + cosmossdk.io/errors v1.0.1 + 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. +retract [v1.1.0, v1.1.1] + +// Bit length differences between Int and Dec +retract ( + v1.3.0 + v1.2.0 + v1.1.2 + [v1.0.0, v1.0.1] ) diff --git a/math/go.sum b/math/go.sum index ed39fb0288..bc3671d5b7 100644 --- a/math/go.sum +++ b/math/go.sum @@ -1,26 +1,49 @@ +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/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= +github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= 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/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +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/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/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/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= +github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 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/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.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +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/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250122153221-138b5a5a4fd4 h1:yrTuav+chrF0zF/joFGICKTzYv7mh/gr9AgEXrVU8ao= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250122153221-138b5a5a4fd4/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= +google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= +google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= +google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 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.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +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/int.go b/math/int.go index dc945b13d2..12217981a4 100644 --- a/math/int.go +++ b/math/int.go @@ -3,8 +3,10 @@ package math import ( "encoding" "encoding/json" + "errors" "fmt" "math/big" + "math/bits" "strings" "sync" "testing" @@ -13,6 +15,20 @@ import ( // MaxBitLen defines the maximum bit length supported bit Int and Uint types. const MaxBitLen = 256 +// maxWordLen defines the maximum word length supported by Int and Uint types. +// We check overflow, by first doing a fast check if the word length is below maxWordLen +// and if not then do the slower full bitlen check. +// NOTE: If MaxBitLen is not a multiple of bits.UintSize, then we need to edit the used logic slightly. +const maxWordLen = MaxBitLen / bits.UintSize + +// Integer errors +var ( + // ErrIntOverflow is the error returned when an integer overflow occurs + ErrIntOverflow = errors.New("integer overflow") + // ErrDivideByZero is the error returned when a divide by zero occurs + ErrDivideByZero = errors.New("divide by zero") +) + func newIntegerFromString(s string) (*big.Int, bool) { return new(big.Int).SetString(s, 0) } @@ -62,7 +78,7 @@ func unmarshalText(i *big.Int, text string) error { return err } - if i.BitLen() > MaxBitLen { + if bigIntOverflows(i) { return fmt.Errorf("integer out of range: %s", text) } @@ -71,7 +87,7 @@ func unmarshalText(i *big.Int, text string) error { var _ customProtobufType = (*Int)(nil) -// Int wraps big.Int with a 257 bit range bound +// Int wraps big.Int with a 256 bit range bound // Checks overflow, underflow and division by zero // Exists in range from -(2^256 - 1) to 2^256 - 1 type Int struct { @@ -86,6 +102,14 @@ func (i Int) BigInt() *big.Int { return new(big.Int).Set(i.i) } +// BigIntMut converts Int to big.Int, mutative the input +func (i Int) BigIntMut() *big.Int { + if i.IsNil() { + return nil + } + return i.i +} + // IsNil returns true if Int is uninitialized func (i Int) IsNil() bool { return i.i == nil @@ -105,14 +129,31 @@ func NewIntFromUint64(n uint64) Int { // NewIntFromBigInt constructs Int from big.Int. If the provided big.Int is nil, // it returns an empty instance. This function panics if the bit length is > 256. +// Note, the caller can safely mutate the argument after this function returns. func NewIntFromBigInt(i *big.Int) Int { if i == nil { return Int{} } - if i.BitLen() > MaxBitLen { + if bigIntOverflows(i) { panic("NewIntFromBigInt() out of bound") } + + return Int{new(big.Int).Set(i)} +} + +// NewIntFromBigIntMut constructs Int from big.Int. If the provided big.Int is nil, +// it returns an empty instance. This function panics if the bit length is > 256. +// Note, this function mutate the argument. +func NewIntFromBigIntMut(i *big.Int) Int { + if i == nil { + return Int{} + } + + if bigIntOverflows(i) { + panic("NewIntFromBigInt() out of bound") + } + return Int{i} } @@ -123,7 +164,7 @@ func NewIntFromString(s string) (res Int, ok bool) { return } // Check overflow - if i.BitLen() > MaxBitLen { + if bigIntOverflows(i) { ok = false return } @@ -141,7 +182,7 @@ func NewIntWithDecimal(n int64, dec int) Int { i.Mul(big.NewInt(n), exp) // Check overflow - if i.BitLen() > MaxBitLen { + if bigIntOverflows(i) { panic("NewIntWithDecimal() out of bound") } return Int{i} @@ -153,6 +194,11 @@ func ZeroInt() Int { return Int{big.NewInt(0)} } // OneInt returns Int value with one func OneInt() Int { return Int{big.NewInt(1)} } +// ToLegacyDec converts Int to LegacyDec +func (i Int) ToLegacyDec() LegacyDec { + return LegacyNewDecFromInt(i) +} + // Int64 converts Int to int64 // Panics if the value is out of range func (i Int) Int64() int64 { @@ -229,12 +275,12 @@ func (i Int) LTE(i2 Int) bool { // Add adds Int from another func (i Int) Add(i2 Int) (res Int) { - res = Int{add(i.i, i2.i)} // Check overflow - if res.i.BitLen() > MaxBitLen { - panic("Int overflow") + x, err := i.SafeAdd(i2) + if err != nil { + panic(err) } - return + return x } // AddRaw adds int64 to Int @@ -242,14 +288,24 @@ func (i Int) AddRaw(i2 int64) Int { return i.Add(NewInt(i2)) } +// SafeAdd adds Int from another and returns an error if overflow +func (i Int) SafeAdd(i2 Int) (res Int, err error) { + res = Int{add(i.i, i2.i)} + // Check overflow + if bigIntOverflows(res.i) { + return Int{}, ErrIntOverflow + } + return res, nil +} + // Sub subtracts Int from another func (i Int) Sub(i2 Int) (res Int) { - res = Int{sub(i.i, i2.i)} // Check overflow - if res.i.BitLen() > MaxBitLen { - panic("Int overflow") + x, err := i.SafeSub(i2) + if err != nil { + panic(err) } - return + return x } // SubRaw subtracts int64 from Int @@ -257,32 +313,49 @@ func (i Int) SubRaw(i2 int64) Int { return i.Sub(NewInt(i2)) } +// SafeSub subtracts Int from another and returns an error if overflow or underflow +func (i Int) SafeSub(i2 Int) (res Int, err error) { + res = Int{sub(i.i, i2.i)} + // Check overflow/underflow + if bigIntOverflows(res.i) { + return Int{}, ErrIntOverflow + } + return res, nil +} + // Mul multiples two Ints func (i Int) Mul(i2 Int) (res Int) { // Check overflow - if i.i.BitLen()+i2.i.BitLen()-1 > MaxBitLen { - panic("Int overflow") + x, err := i.SafeMul(i2) + if err != nil { + panic(err) } - res = Int{mul(i.i, i2.i)} - // Check overflow if sign of both are same - if res.i.BitLen() > MaxBitLen { - panic("Int overflow") - } - return + return x } -// MulRaw multipies Int and int64 +// MulRaw multiplies Int and int64 func (i Int) MulRaw(i2 int64) Int { return i.Mul(NewInt(i2)) } +// SafeMul multiples Int from another and returns an error if overflow +func (i Int) SafeMul(i2 Int) (res Int, err error) { + res = Int{mul(i.i, i2.i)} + // Check overflow + if bigIntOverflows(res.i) { + return Int{}, ErrIntOverflow + } + return res, nil +} + // Quo divides Int with Int func (i Int) Quo(i2 Int) (res Int) { // Check division-by-zero - if i2.i.Sign() == 0 { + x, err := i.SafeQuo(i2) + if err != nil { panic("Division by zero") } - return Int{div(i.i, i2.i)} + return x } // QuoRaw divides Int with int64 @@ -290,12 +363,22 @@ func (i Int) QuoRaw(i2 int64) Int { return i.Quo(NewInt(i2)) } +// SafeQuo divides Int with Int and returns an error if division by zero +func (i Int) SafeQuo(i2 Int) (res Int, err error) { + // Check division-by-zero + if i2.i.Sign() == 0 { + return Int{}, ErrDivideByZero + } + return Int{div(i.i, i2.i)}, nil +} + // Mod returns remainder after dividing with Int func (i Int) Mod(i2 Int) Int { - if i2.Sign() == 0 { - panic("division-by-zero") + x, err := i.SafeMod(i2) + if err != nil { + panic(err) } - return Int{mod(i.i, i2.i)} + return x } // ModRaw returns remainder after dividing with int64 @@ -303,6 +386,14 @@ func (i Int) ModRaw(i2 int64) Int { return i.Mod(NewInt(i2)) } +// SafeMod returns remainder after dividing with Int and returns an error if division by zero +func (i Int) SafeMod(i2 Int) (res Int, err error) { + if i2.Sign() == 0 { + return Int{}, ErrDivideByZero + } + return Int{mod(i.i, i2.i)}, nil +} + // Neg negates Int func (i Int) Neg() (res Int) { return Int{neg(i.i)} @@ -313,7 +404,7 @@ func (i Int) Abs() Int { return Int{abs(i.i)} } -// return the minimum of the ints +// MinInt return the minimum of the ints func MinInt(i1, i2 Int) Int { return Int{min(i1.BigInt(), i2.BigInt())} } @@ -323,7 +414,7 @@ func MaxInt(i, i2 Int) Int { return Int{max(i.BigInt(), i2.BigInt())} } -// Human readable string +// String returns human-readable string func (i Int) String() string { return i.i.String() } @@ -344,7 +435,7 @@ func (i *Int) UnmarshalJSON(bz []byte) error { return unmarshalJSON(i.i, bz) } -// MarshalJSON for custom encoding scheme +// marshalJSON for custom encoding scheme // Must be encoded as a string for JSON precision func marshalJSON(i encoding.TextMarshaler) ([]byte, error) { text, err := i.MarshalText() @@ -355,7 +446,7 @@ func marshalJSON(i encoding.TextMarshaler) ([]byte, error) { return json.Marshal(string(text)) } -// UnmarshalJSON for custom decoding scheme +// unmarshalJSON for custom decoding scheme // Must be encoded as a string for JSON precision func unmarshalJSON(i *big.Int, bz []byte) error { var text string @@ -413,7 +504,7 @@ func (i *Int) Unmarshal(data []byte) error { return err } - if i.i.BitLen() > MaxBitLen { + if bigIntOverflows(i.i) { return fmt.Errorf("integer out of range; got: %d, max: %d", i.i.BitLen(), MaxBitLen) } @@ -426,12 +517,13 @@ func (i *Int) Size() int { return len(bz) } -// Override Amino binary serialization by proxying to protobuf. +// MarshalAmino Override Amino binary serialization by proxying to protobuf. func (i Int) MarshalAmino() ([]byte, error) { return i.Marshal() } func (i *Int) UnmarshalAmino(bz []byte) error { return i.Unmarshal(bz) } -// intended to be used with require/assert: require.True(IntEq(...)) +// IntEq intended to be used with require/assert: require.True(IntEq(...)) func IntEq(t *testing.T, exp, got Int) (*testing.T, bool, string, string, string) { + t.Helper() return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp.String(), got.String() } @@ -458,7 +550,7 @@ var stringsBuilderPool = &sync.Pool{ // (instead of manipulating the int or math.Int object). func FormatInt(v string) (string, error) { if len(v) == 0 { - return "", fmt.Errorf("cannot format empty string") + return "", errors.New("cannot format empty string") } sign := "" @@ -506,3 +598,15 @@ func FormatInt(v string) (string, error) { return sign + sb.String(), nil } + +// check if the big int overflows. +func bigIntOverflows(i *big.Int) bool { + // overflow is defined as i.BitLen() > MaxBitLen + // however this check can be expensive when doing many operations. + // So we first check if the word length is greater than maxWordLen. + // However the most significant word could be zero, hence we still do the bitlen check. + if len(i.Bits()) > maxWordLen { + return i.BitLen() > MaxBitLen + } + return false +} diff --git a/math/int_test.go b/math/int_test.go index 56b05cc69f..e09caf6ae5 100644 --- a/math/int_test.go +++ b/math/int_test.go @@ -43,6 +43,57 @@ func (s *intTestSuite) TestFromUint64() { } } +func (s *intTestSuite) TestNewIntFromBigInt() { + i := math.NewIntFromBigInt(nil) + s.Require().True(i.IsNil()) + + r := big.NewInt(42) + i = math.NewIntFromBigInt(r) + s.Require().Equal(r, i.BigInt()) + + // modify r and ensure i doesn't change + r = r.SetInt64(100) + s.Require().NotEqual(r, i.BigInt()) +} + +func (s *intTestSuite) TestNewIntFromBigIntMut() { + im := math.NewIntFromBigIntMut(nil) + s.Require().True(im.IsNil()) + + r := big.NewInt(42) + im = math.NewIntFromBigIntMut(r) + s.Require().Equal(r, im.BigInt()) + + // Compare value of NewIntFromBigInt and NewIntFromBigIntMut + i := math.NewIntFromBigInt(r) + s.Require().Equal(i, im) + + // modify r and ensure i doesn't change & im changes + r = r.SetInt64(100) + s.Require().NotEqual(r, i.BigInt()) + s.Require().Equal(r, im.BigInt()) +} + +func (s *intTestSuite) TestConvertToBigIntMutative() { + r := big.NewInt(42) + i := math.NewIntFromBigInt(r) + + // Compare value of BigInt & BigIntMut + s.Require().Equal(i.BigInt(), i.BigIntMut()) + + // Modify BigIntMut() pointer and ensure i.BigIntMut() & i.BigInt() change + p := i.BigIntMut() + p.SetInt64(50) + s.Require().Equal(big.NewInt(50), i.BigIntMut()) + s.Require().Equal(big.NewInt(50), i.BigInt()) + + // Modify big.Int() pointer and ensure i.BigIntMut() & i.BigInt() don't change + p = i.BigInt() + p.SetInt64(60) + s.Require().NotEqual(big.NewInt(60), i.BigIntMut()) + s.Require().NotEqual(big.NewInt(60), i.BigInt()) +} + func (s *intTestSuite) TestIntPanic() { // Max Int = 2^256-1 = 1.1579209e+77 // Min Int = -(2^256-1) = -1.1579209e+77 @@ -60,32 +111,66 @@ func (s *intTestSuite) TestIntPanic() { s.Require().NotPanics(func() { i1.Add(i1) }) s.Require().NotPanics(func() { i2.Add(i2) }) s.Require().Panics(func() { i3.Add(i3) }) + _, err := i1.SafeAdd(i1) + s.Require().Nil(err) + _, err = i2.SafeAdd(i2) + s.Require().Nil(err) + _, err = i3.SafeAdd(i3) + s.Require().Error(err) s.Require().NotPanics(func() { i1.Sub(i1.Neg()) }) s.Require().NotPanics(func() { i2.Sub(i2.Neg()) }) s.Require().Panics(func() { i3.Sub(i3.Neg()) }) + _, err = i1.SafeSub(i1.Neg()) + s.Require().Nil(err) + _, err = i2.SafeSub(i2.Neg()) + s.Require().Nil(err) + _, err = i3.SafeSub(i3.Neg()) + s.Require().Error(err) s.Require().Panics(func() { i1.Mul(i1) }) s.Require().Panics(func() { i2.Mul(i2) }) s.Require().Panics(func() { i3.Mul(i3) }) + _, err = i1.SafeMul(i1) + s.Require().Error(err) + _, err = i2.SafeMul(i2) + s.Require().Error(err) + _, err = i3.SafeMul(i3) + s.Require().Error(err) s.Require().Panics(func() { i1.Neg().Mul(i1.Neg()) }) s.Require().Panics(func() { i2.Neg().Mul(i2.Neg()) }) s.Require().Panics(func() { i3.Neg().Mul(i3.Neg()) }) + _, err = i1.Neg().SafeMul(i1.Neg()) + s.Require().Error(err) + _, err = i2.Neg().SafeMul(i2.Neg()) + s.Require().Error(err) + _, err = i3.Neg().SafeMul(i3.Neg()) + s.Require().Error(err) - // // Underflow check + // Underflow check i3n := i3.Neg() s.Require().NotPanics(func() { i3n.Sub(i1) }) s.Require().NotPanics(func() { i3n.Sub(i2) }) s.Require().Panics(func() { i3n.Sub(i3) }) + _, err = i3n.SafeSub(i3) + s.Require().Error(err) s.Require().NotPanics(func() { i3n.Add(i1.Neg()) }) s.Require().NotPanics(func() { i3n.Add(i2.Neg()) }) s.Require().Panics(func() { i3n.Add(i3.Neg()) }) + _, err = i3n.SafeAdd(i3.Neg()) + s.Require().Error(err) s.Require().Panics(func() { i1.Mul(i1.Neg()) }) s.Require().Panics(func() { i2.Mul(i2.Neg()) }) s.Require().Panics(func() { i3.Mul(i3.Neg()) }) + _, err = i1.SafeMul(i1.Neg()) + s.Require().Error(err) + _, err = i2.SafeMul(i2.Neg()) + s.Require().Error(err) + _, err = i3.SafeMul(i3.Neg()) + s.Require().Error(err) // Bound check intmax := math.NewIntFromBigInt(new(big.Int).Sub(new(big.Int).Exp(big.NewInt(2), big.NewInt(256), nil), big.NewInt(1))) @@ -94,12 +179,18 @@ func (s *intTestSuite) TestIntPanic() { s.Require().NotPanics(func() { intmin.Sub(math.ZeroInt()) }) s.Require().Panics(func() { intmax.Add(math.OneInt()) }) s.Require().Panics(func() { intmin.Sub(math.OneInt()) }) + _, err = intmax.SafeAdd(math.OneInt()) + s.Require().Error(err) + _, err = intmin.SafeSub(math.OneInt()) + s.Require().Error(err) s.Require().NotPanics(func() { math.NewIntFromBigInt(nil) }) s.Require().True(math.NewIntFromBigInt(nil).IsNil()) // Division-by-zero check s.Require().Panics(func() { i1.Quo(math.NewInt(0)) }) + _, err = i1.SafeQuo(math.NewInt(0)) + s.Require().Error(err) s.Require().NotPanics(func() { math.Int{}.BigInt() }) } @@ -408,7 +499,6 @@ func TestRoundTripMarshalToInt(t *testing.T) { } for _, value := range values { - value := value t.Run(fmt.Sprintf("%d", value), func(t *testing.T) { t.Parallel() @@ -457,7 +547,6 @@ func TestFormatIntNonDigits(t *testing.T) { } for _, value := range badCases { - value := value t.Run(value, func(t *testing.T) { s, err := math.FormatInt(value) if err == nil { @@ -500,7 +589,6 @@ func TestFormatIntCorrectness(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.in, func(t *testing.T) { got, err := math.FormatInt(tt.in) if err != nil { @@ -513,3 +601,108 @@ func TestFormatIntCorrectness(t *testing.T) { }) } } + +var sizeTests = []struct { + s string + want int +}{ + {"", 1}, + {"0", 1}, + {"-0", 1}, + {"-10", 3}, + {"-10000", 6}, + {"10000", 5}, + {"100000", 6}, + {"99999", 5}, + {"9999999999", 10}, + {"10000000000", 11}, + {"99999999999", 11}, + {"999999999999", 12}, + {"9999999999999", 13}, + {"99999999999999", 14}, + {"999999999999999", 15}, + {"1000000000000000", 16}, + {"9999999999999999", 16}, + {"99999999999999999", 17}, + {"999999999999999999", 18}, + {"-999999999999999999", 19}, + {"9000000000000000000", 19}, + {"-9999999999999990000", 20}, + {"9999999999999990000", 19}, + {"9999999999999999000", 19}, + {"9999999999999999999", 19}, + {"-9999999999999999999", 20}, + {"18446744073709551616", 20}, + {"18446744073709551618", 20}, + {"184467440737095516181", 21}, + {"100000000000000000000000", 24}, + {"1000000000000000000000000000", 28}, + {"9000000000099999999999999999", 28}, + {"9999999999999999999999999999", 28}, + {"9903520314283042199192993792", 28}, + {"340282366920938463463374607431768211456", 39}, + {"3402823669209384634633746074317682114569999", 43}, + {"9999999999999999999999999999999999999999999", 43}, + {"99999999999999999999999999999999999999999999", 44}, + {"999999999999999999999999999999999999999999999", 45}, + {"90000000000999999999999999999000000000099999999999999999", 56}, + {"-90000000000999999999999999999000000000099999999999999999", 57}, + {"9000000000099999999999999999900000000009999999999999999990", 58}, + {"990000000009999999999999999990000000000999999999999999999999", 60}, + {"99000000000999999999999999999000000000099999999999999999999919", 62}, + {"90000000000999999990000000000000000000000000000000000000000000", 62}, + {"99999999999999999999999999990000000000000000000000000000000000", 62}, + {"11111111111111119999999999990000000000000000000000000000000000", 62}, + {"99000000000999999999999999999000000000099999999999999999999919", 62}, + {"10000000000000000000000000000000000000000000000000000000000000", 62}, + {"10000000000000000000000000000000000000000000000000000000000000000000000000000", 77}, + {"99999999999999999999999999999999999999999999999999999999999999999999999999999", 77}, + {"110000000000000000000000000000000000000000000000000000000000000000000000000009", 78}, +} + +func TestNewIntFromString(t *testing.T) { + for _, st := range sizeTests { + ii, _ := math.NewIntFromString(st.s) + require.Equal(t, st.want, ii.Size(), "size mismatch for %q", st.s) + } +} + +func BenchmarkIntSize(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + for _, st := range sizeTests { + ii, _ := math.NewIntFromString(st.s) + got := ii.Size() + if got != st.want { + b.Errorf("%q:: got=%d, want=%d", st.s, got, st.want) + } + sink = got + } + } + if sink == nil { + b.Fatal("Benchmark did not run!") + } + sink = nil +} + +func BenchmarkIntOverflowCheckTime(b *testing.B) { + ints := []*big.Int{} + + for _, st := range sizeTests { + ii, _ := math.NewIntFromString(st.s) + ints = append(ints, ii.BigInt()) + } + b.ResetTimer() + + b.ReportAllocs() + for i := 0; i < b.N; i++ { + for j := range sizeTests { + got := math.NewIntFromBigIntMut(ints[j]) + sink = got + } + } + if sink == nil { + b.Fatal("Benchmark did not run!") + } + sink = nil +} diff --git a/math/legacy_dec.go b/math/legacy_dec.go new file mode 100644 index 0000000000..16bb080686 --- /dev/null +++ b/math/legacy_dec.go @@ -0,0 +1,971 @@ +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]] + // Deprecated: This is unused and will be removed + LegacyDecimalPrecisionBits = 60 + + // 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)) + + upperLimit LegacyDec + lowerLimit LegacyDec + + 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)) + } + // 2^256 * 10^18 -1 + tmp := new(big.Int).Exp(big.NewInt(2), big.NewInt(256), nil) + tmp = new(big.Int).Sub(new(big.Int).Mul(tmp, precisionReuse), big.NewInt(1)) + upperLimit = LegacyNewDecFromBigIntWithPrec(tmp, LegacyPrecision) + lowerLimit = upperLimit.Neg() +} + +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 neg { + combined = new(big.Int).Neg(combined) + } + + result := LegacyDec{i: combined} + if !result.IsInValidRange() { + return LegacyDec{}, fmt.Errorf("out of range: %w", ErrLegacyInvalidDecimalStr) + } + return result, 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) + + d.assertInValidRange() + 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) + + d.assertInValidRange() + return d +} + +func (d LegacyDec) assertInValidRange() { + if !d.IsInValidRange() { + panic("Int overflow") + } +} + +// IsInValidRange returns true when the value is between the upper limit of (2^256 * 10^18) +// and the lower limit of -1*(2^256 * 10^18). +func (d LegacyDec) IsInValidRange() bool { + return !(d.GT(upperLimit) || d.LT(lowerLimit)) +} + +// 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) + + *d.i = *chopped + d.assertInValidRange() + 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) + d.assertInValidRange() + 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) + + d.assertInValidRange() + 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()) + d.assertInValidRange() + 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)) + d.assertInValidRange() + 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) + d.assertInValidRange() + 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) + + d.assertInValidRange() + 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) + } + d.assertInValidRange() + 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 root == 0 { + // Return 1 as root 0 of any number is considered 1. + return LegacyOneDec(), nil + } + + if d.IsNegative() { + absRoot, err := d.Neg().ApproxRoot(root) + return absRoot.NegMut(), err + } + + // Direct return for base cases: d^1 = d or when d is 0 or 1. + scratchOneDec := LegacyOneDec() + if root == 1 || d.IsZero() || d.Equal(scratchOneDec) { + return d, nil + } + + guess, delta := scratchOneDec, LegacyOneDec() + + for iter := 0; iter < maxApproxRootIterations; iter++ { + prev := guess.Power(root - 1) + if prev.IsZero() { + prev = smallestDec + } + + // Compute delta = (d/prev - guess) / root + delta.Set(d).QuoMut(prev) + delta.SubMut(guess) + delta.QuoInt64Mut(int64(root)) + + guess.AddMut(delta) + + // Stop when delta is small enough + if delta.Abs().LTE(smallestDec) { + break + } + } + + return guess, nil +} + +// Power returns 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 + var r LegacyDec + switch rem.Sign() { + case 0: + r = LegacyNewDecFromBigInt(quo) + case -1: + r = LegacyNewDecFromBigInt(quo) + default: + r = LegacyNewDecFromBigInt(quo.Add(quo, oneInt)) + } + r.assertInValidRange() + return r +} + +// 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.IsInValidRange() { + return errors.New("decimal out of range") + } + 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 + +// LegacyDecsEqual return true 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 +} diff --git a/math/fuzz_test.go b/math/legacy_dec_fuzz_test.go similarity index 73% rename from math/fuzz_test.go rename to math/legacy_dec_fuzz_test.go index 3fae5a7e5c..e50ae41bfa 100644 --- a/math/fuzz_test.go +++ b/math/legacy_dec_fuzz_test.go @@ -22,9 +22,3 @@ func FuzzLegacyNewDecFromStr(f *testing.F) { } }) } - -func TestDecNegativePrecision(t *testing.T) { - t.Skip("https://github.com/cosmos/cosmos-sdk/issues/14004 is not yet addressed") - - LegacyNewDecWithPrec(10, -1) -} diff --git a/math/dec_internal_test.go b/math/legacy_dec_internal_test.go similarity index 99% rename from math/dec_internal_test.go rename to math/legacy_dec_internal_test.go index 8b899300e3..5273edfc9b 100644 --- a/math/dec_internal_test.go +++ b/math/legacy_dec_internal_test.go @@ -90,7 +90,6 @@ func (s *decimalInternalTestSuite) TestDecMarshalJSON() { {"12340Int", LegacyNewDec(12340), "\"12340.000000000000000000\"", false}, } for _, tt := range tests { - tt := tt s.T().Run(tt.name, func(t *testing.T) { got, err := tt.d.MarshalJSON() if (err != nil) != tt.wantErr { diff --git a/math/legacy_dec_test.go b/math/legacy_dec_test.go new file mode 100644 index 0000000000..1f0b288a08 --- /dev/null +++ b/math/legacy_dec_test.go @@ -0,0 +1,1328 @@ +package math_test + +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 + }{ + {"", 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{}}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639936", true, math.LegacyDec{}}, // 2^256 + } + + 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), + }, + { + 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 { + + 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() { + // (2^256 * 10^18 -1) / 10^18 + d, err := math.LegacyNewDecFromStr("115792089237316195423570985008687907853269984665640564039457584007913129639935.999999999999999999") + s.Require().NoError(err) + s.Require().True(d.IsInValidRange()) + // 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.LegacyNewDecFromInt(math.NewInt(2)), 0, math.LegacyOneDec()}, // 2 ^ 0 => 1.0 + {math.LegacyNewDecWithPrec(4, 2), 0, math.LegacyOneDec()}, // 0.04 ^ 0 => 1.0 + {math.LegacyNewDec(0), 1, math.LegacyNewDec(0)}, // 0 ^ 1 => 0 + {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 { + 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 { + 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()) + }) + }) + }) + } +} + +func Test_DocumentLegacyAsymmetry(t *testing.T) { + zeroDec := math.LegacyZeroDec() + emptyDec := math.LegacyDec{} + + zeroDecBz, err := zeroDec.Marshal() + require.NoError(t, err) + zeroDecJSON, err := zeroDec.MarshalJSON() + require.NoError(t, err) + + emptyDecBz, err := emptyDec.Marshal() + require.NoError(t, err) + emptyDecJSON, err := emptyDec.MarshalJSON() + require.NoError(t, err) + + // makes sense, zero and empty are semantically different and render differently + require.NotEqual(t, zeroDecJSON, emptyDecJSON) + // but on the proto wire they encode to the same bytes + require.Equal(t, zeroDecBz, emptyDecBz) + + // zero values are symmetrical + zeroDecRoundTrip := math.LegacyDec{} + err = zeroDecRoundTrip.Unmarshal(zeroDecBz) + require.NoError(t, err) + zeroDecRoundTripJSON, err := zeroDecRoundTrip.MarshalJSON() + require.NoError(t, err) + require.Equal(t, zeroDecJSON, zeroDecRoundTripJSON) + require.Equal(t, zeroDec, zeroDecRoundTrip) + + // empty values are not + emptyDecRoundTrip := math.LegacyDec{} + err = emptyDecRoundTrip.Unmarshal(emptyDecBz) + require.NoError(t, err) + emptyDecRoundTripJSON, err := emptyDecRoundTrip.MarshalJSON() + require.NoError(t, err) + + // !!! this is the key point, they are not equal, it looks like a bug + require.NotEqual(t, emptyDecJSON, emptyDecRoundTripJSON) + require.NotEqual(t, emptyDec, emptyDecRoundTrip) +} + +// 2^256 * 10^18 -1 +const maxValidDecNumber = "115792089237316195423570985008687907853269984665640564039457584007913129639935999999999999999999" + +func TestDecOpsWithinLimits(t *testing.T) { + maxValid, ok := new(big.Int).SetString(maxValidDecNumber, 10) + require.True(t, ok) + minValid := new(big.Int).Neg(maxValid) + specs := map[string]struct { + src *big.Int + expErr bool + }{ + "max": { + src: maxValid, + }, + "max + 1": { + src: new(big.Int).Add(maxValid, big.NewInt(1)), + expErr: true, + }, + "min": { + src: minValid, + }, + "min - 1": { + src: new(big.Int).Sub(minValid, big.NewInt(1)), + expErr: true, + }, + "max Int": { + // max Int is 2^256 -1 + src: math.NewIntFromBigInt(new(big.Int).Sub(new(big.Int).Exp(big.NewInt(2), big.NewInt(256), nil), big.NewInt(1))).BigIntMut(), + }, + "min Int": { + // max Int is -1 *(2^256 -1) + src: math.NewIntFromBigInt(new(big.Int).Neg(new(big.Int).Sub(new(big.Int).Exp(big.NewInt(2), big.NewInt(256), nil), big.NewInt(1)))).BigIntMut(), + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + src := math.LegacyNewDecFromBigIntWithPrec(spec.src, 18) + + ops := map[string]struct { + fn func(src math.LegacyDec) math.LegacyDec + }{ + "AddMut": { + fn: func(src math.LegacyDec) math.LegacyDec { return src.AddMut(math.LegacyNewDec(0)) }, + }, + "SubMut": { + fn: func(src math.LegacyDec) math.LegacyDec { return src.SubMut(math.LegacyNewDec(0)) }, + }, + "MulMut": { + fn: func(src math.LegacyDec) math.LegacyDec { return src.MulMut(math.LegacyNewDec(1)) }, + }, + "MulTruncateMut": { + fn: func(src math.LegacyDec) math.LegacyDec { return src.MulTruncateMut(math.LegacyNewDec(1)) }, + }, + "MulRoundUpMut": { + fn: func(src math.LegacyDec) math.LegacyDec { return src.MulRoundUpMut(math.LegacyNewDec(1)) }, + }, + "MulIntMut": { + fn: func(src math.LegacyDec) math.LegacyDec { return src.MulIntMut(math.NewInt(1)) }, + }, + "MulInt64Mut": { + fn: func(src math.LegacyDec) math.LegacyDec { return src.MulInt64Mut(1) }, + }, + "QuoMut": { + fn: func(src math.LegacyDec) math.LegacyDec { return src.QuoMut(math.LegacyNewDec(1)) }, + }, + "QuoTruncateMut": { + fn: func(src math.LegacyDec) math.LegacyDec { return src.QuoTruncateMut(math.LegacyNewDec(1)) }, + }, + "QuoRoundupMut": { + fn: func(src math.LegacyDec) math.LegacyDec { return src.QuoRoundupMut(math.LegacyNewDec(1)) }, + }, + } + for name, op := range ops { + t.Run(name, func(t *testing.T) { + if spec.expErr { + assert.Panics(t, func() { + got := op.fn(src) + t.Log(got.String()) + }) + return + } + exp := src.String() + // exp no panics + got := op.fn(src) + assert.Equal(t, exp, got.String()) + }) + } + }) + } +} + +func TestDecCeilLimits(t *testing.T) { + maxValid, ok := new(big.Int).SetString(maxValidDecNumber, 10) + require.True(t, ok) + minValid := new(big.Int).Neg(maxValid) + + specs := map[string]struct { + src *big.Int + exp string + expErr bool + }{ + "max": { + src: maxValid, + expErr: true, + }, + "max + 1": { + src: new(big.Int).Add(maxValid, big.NewInt(1)), + expErr: true, + }, + "max - 1e18, previous full number": { + src: new(big.Int).Sub(maxValid, new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil)), + exp: "115792089237316195423570985008687907853269984665640564039457584007913129639935.000000000000000000", + }, + "min": { + src: minValid, + exp: "-115792089237316195423570985008687907853269984665640564039457584007913129639935.000000000000000000", + }, + "min - 1": { + src: new(big.Int).Sub(minValid, big.NewInt(1)), + expErr: true, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + src := math.LegacyNewDecFromBigIntWithPrec(spec.src, 18) + if spec.expErr { + assert.Panics(t, func() { + got := src.Ceil() + t.Log(got.String()) + }) + return + } + got := src.Ceil() + assert.Equal(t, spec.exp, got.String()) + }) + } +} + +func TestTruncateIntLimits(t *testing.T) { + maxValid, ok := new(big.Int).SetString(maxValidDecNumber, 10) + require.True(t, ok) + minValid := new(big.Int).Neg(maxValid) + + specs := map[string]struct { + src *big.Int + exp string + expErr bool + }{ + "max": { + src: maxValid, + exp: "115792089237316195423570985008687907853269984665640564039457584007913129639935", + }, + "max + 1": { + src: new(big.Int).Add(maxValid, big.NewInt(1)), + expErr: true, + }, + "min": { + src: minValid, + exp: "-115792089237316195423570985008687907853269984665640564039457584007913129639935", + }, + "min - 1": { + src: new(big.Int).Sub(minValid, big.NewInt(1)), + expErr: true, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + src := math.LegacyNewDecFromBigIntWithPrec(spec.src, 18) + if spec.expErr { + assert.Panics(t, func() { + got := src.TruncateInt() + t.Log(got.String()) + }) + return + } + got := src.TruncateInt() + assert.Equal(t, spec.exp, got.String()) + }) + } +} + +func TestRoundIntLimits(t *testing.T) { + maxValid, ok := new(big.Int).SetString(maxValidDecNumber, 10) + require.True(t, ok) + minValid := new(big.Int).Neg(maxValid) + oneE18 := new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil) + + specs := map[string]struct { + src *big.Int + exp string + expErr bool + }{ + "max -1e18; previous full number": { + src: new(big.Int).Sub(maxValid, oneE18), + exp: "115792089237316195423570985008687907853269984665640564039457584007913129639935", + }, + "max": { + src: maxValid, + expErr: true, + }, + "max + 1": { + src: new(big.Int).Add(maxValid, big.NewInt(1)), + expErr: true, + }, + "min + 1e18; previous full number": { + src: new(big.Int).Add(minValid, oneE18), + exp: "-115792089237316195423570985008687907853269984665640564039457584007913129639935", + }, + "min": { + src: minValid, + expErr: true, + }, + "min - 1": { + src: new(big.Int).Sub(minValid, big.NewInt(1)), + expErr: true, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + src := math.LegacyNewDecFromBigIntWithPrec(spec.src, 18) + t.Log(src.String()) + if spec.expErr { + assert.Panics(t, func() { + got := src.RoundInt() + t.Log(got.String()) + }) + return + } + got := src.RoundInt() + assert.Equal(t, spec.exp, got.String()) + }) + } +} + +func BenchmarkIsInValidRange(b *testing.B) { + maxValid, ok := new(big.Int).SetString(maxValidDecNumber, 10) + require.True(b, ok) + souceMax := math.LegacyNewDecFromBigIntWithPrec(maxValid, 18) + b.ResetTimer() + specs := map[string]math.LegacyDec{ + "max": souceMax, + "greater max": math.LegacyNewDecFromBigIntWithPrec(maxValid, 16), + "min": souceMax.Neg(), + "lower min": math.LegacyNewDecFromBigIntWithPrec(new(big.Int).Neg(maxValid), 16), + "zero": math.LegacyZeroDec(), + "one": math.LegacyOneDec(), + } + for name, source := range specs { + b.Run(name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = source.IsInValidRange() + } + }) + } +} diff --git a/math/max_min.go b/math/max_min.go index 5857320067..bf38113842 100644 --- a/math/max_min.go +++ b/math/max_min.go @@ -1,8 +1,6 @@ package math -import ( - "cmp" -) +import "cmp" func Max[T cmp.Ordered](a, b T, rest ...T) T { max := a diff --git a/math/sonar-project.properties b/math/sonar-project.properties new file mode 100644 index 0000000000..8f90cb222b --- /dev/null +++ b/math/sonar-project.properties @@ -0,0 +1,16 @@ +sonar.projectKey=cosmos-sdk-math +sonar.organization=cosmos + +sonar.projectName=Cosmos SDK - Math +sonar.project.monorepo.enabled=true + +sonar.sources=. +sonar.exclusions=**/*_test.go,**/*.pb.go,**/*.pulsar.go,**/*.pb.gw.go +sonar.coverage.exclusions=**/*_test.go,**/testutil/**,**/*.pb.go,**/*.pb.gw.go,**/*.pulsar.go,test_helpers.go,docs/** +sonar.tests=. +sonar.test.inclusions=**/*_test.go +sonar.go.coverage.reportPaths=coverage.out + +sonar.sourceEncoding=UTF-8 +sonar.scm.provider=git +sonar.scm.forceReloadAll=true diff --git a/math/uint.go b/math/uint.go index 0a61646ae1..b780633ef4 100644 --- a/math/uint.go +++ b/math/uint.go @@ -15,24 +15,36 @@ type Uint struct { // BigInt converts Uint to big.Int func (u Uint) BigInt() *big.Int { + if u.IsNil() { + return nil + } return new(big.Int).Set(u.i) } +// BigIntMut converts Uint to big.Int, mutative the input +func (u Uint) BigIntMut() *big.Int { + if u.IsNil() { + return nil + } + return u.i +} + // IsNil returns true if Uint is uninitialized func (u Uint) IsNil() bool { return u.i == nil } -// NewUintFromBigUint constructs Uint from big.Uint +// NewUintFromBigInt constructs Uint from big.Int +// Panics if i is negative or wider than 256 bits func NewUintFromBigInt(i *big.Int) Uint { u, err := checkNewUint(i) if err != nil { - panic(fmt.Errorf("overflow: %s", err)) + panic(fmt.Errorf("overflow: %w", err)) } return u } -// NewUint constructs Uint from int64 +// NewUint constructs Uint from uint64 func NewUint(n uint64) Uint { i := new(big.Int) i.SetUint64(n) @@ -40,6 +52,7 @@ func NewUint(n uint64) Uint { } // NewUintFromString constructs Uint from string +// Panics if parsed s is negative or wider than 256 bits func NewUintFromString(s string) Uint { u, err := ParseUint(s) if err != nil { @@ -86,7 +99,7 @@ func (u Uint) LTE(u2 Uint) bool { return !u.GT(u2) } // Add adds Uint from another func (u Uint) Add(u2 Uint) Uint { return NewUintFromBigInt(new(big.Int).Add(u.i, u2.i)) } -// Add convert uint64 and add it to Uint +// AddUint64 convert uint64 and add it to Uint func (u Uint) AddUint64(u2 uint64) Uint { return u.Add(NewUint(u2)) } // Sub adds Uint from another @@ -100,13 +113,14 @@ func (u Uint) Mul(u2 Uint) (res Uint) { return NewUintFromBigInt(new(big.Int).Mul(u.i, u2.i)) } -// Mul multiplies two Uints +// MulUint64 multiplies two Uints func (u Uint) MulUint64(u2 uint64) (res Uint) { return u.Mul(NewUint(u2)) } // Quo divides Uint with Uint func (u Uint) Quo(u2 Uint) (res Uint) { return NewUintFromBigInt(div(u.i, u2.i)) } // Mod returns remainder after dividing with Uint +// Panics if u2 is zero func (u Uint) Mod(u2 Uint) Uint { if u2.IsZero() { panic("division-by-zero") @@ -125,16 +139,16 @@ func (u Uint) Decr() Uint { return u.Sub(OneUint()) } -// Quo divides Uint with uint64 +// QuoUint64 divides Uint with uint64 func (u Uint) QuoUint64(u2 uint64) Uint { return u.Quo(NewUint(u2)) } -// Return the minimum of the Uints +// MinUint returns the minimum of the Uints func MinUint(u1, u2 Uint) Uint { return NewUintFromBigInt(min(u1.i, u2.i)) } -// Return the maximum of the Uints +// MaxUint returns the maximum of the Uints func MaxUint(u1, u2 Uint) Uint { return NewUintFromBigInt(max(u1.i, u2.i)) } -// Human readable string +// String returns human-readable string func (u Uint) String() string { return u.i.String() } // MarshalJSON defines custom encoding scheme @@ -205,7 +219,7 @@ func (u *Uint) Size() int { return len(bz) } -// Override Amino binary serialization by proxying to protobuf. +// MarshalAmino override Amino binary serialization by proxying to protobuf. func (u Uint) MarshalAmino() ([]byte, error) { return u.Marshal() } func (u *Uint) UnmarshalAmino(bz []byte) error { return u.Unmarshal(bz) } @@ -235,7 +249,7 @@ func checkNewUint(i *big.Int) (Uint, error) { if err := UintOverflow(i); err != nil { return Uint{}, err } - return Uint{i}, nil + return Uint{new(big.Int).Set(i)}, nil } // RelativePow raises x to the power of n, where x (and the result, z) are scaled by factor b @@ -247,7 +261,7 @@ func RelativePow(x, n, b Uint) (z Uint) { return z } z = ZeroUint() // otherwise 0^a = 0 - return + return z } z = x diff --git a/math/uint_test.go b/math/uint_test.go index 0c464433a0..62a081d515 100644 --- a/math/uint_test.go +++ b/math/uint_test.go @@ -69,6 +69,8 @@ func (s *uintTestSuite) TestUintPanics() { s.Require().Panics(func() { uintmin.Sub(sdkmath.OneUint()) }) s.Require().Panics(func() { uintmin.Decr() }) + s.Require().NotPanics(func() { sdkmath.Uint{}.BigInt() }) + s.Require().Equal(uint64(0), sdkmath.MinUint(sdkmath.ZeroUint(), sdkmath.OneUint()).Uint64()) s.Require().Equal(uint64(1), sdkmath.MaxUint(sdkmath.ZeroUint(), sdkmath.OneUint()).Uint64()) @@ -97,6 +99,26 @@ func (s *uintTestSuite) TestIsNil() { s.Require().True(sdkmath.Uint{}.IsNil()) } +func (s *uintTestSuite) TestConvertToBigIntMutativeForUint() { + r := big.NewInt(30) + i := sdkmath.NewUintFromBigInt(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 (s *uintTestSuite) TestArithUint() { for d := 0; d < 1000; d++ { n1 := uint64(rand.Uint32()) @@ -222,7 +244,7 @@ func (s *uintTestSuite) TestSafeSub() { } for i, tc := range testCases { - tc := tc + if tc.panic { s.Require().Panics(func() { tc.x.Sub(tc.y) }) continue @@ -261,6 +283,16 @@ func (s *uintTestSuite) TestParseUint() { } } +func (s *uintTestSuite) TestNewUintFromBigInt() { + r := big.NewInt(42) + i := sdkmath.NewUintFromBigInt(r) + s.Require().Equal(r, i.BigInt()) + + // modify r and ensure i doesn't change + r = r.SetInt64(100) + s.Require().NotEqual(r, i.BigInt()) +} + func randuint() sdkmath.Uint { return sdkmath.NewUint(rand.Uint64()) } @@ -311,7 +343,6 @@ func TestRoundTripMarshalToUint(t *testing.T) { } for _, value := range values { - value := value t.Run(fmt.Sprintf("%d", value), func(t *testing.T) { t.Parallel()