From 2c6117e820158210165426b4580c843a5b8f22aa Mon Sep 17 00:00:00 2001 From: Alex | Interchain Labs Date: Fri, 4 Apr 2025 15:00:25 -0400 Subject: [PATCH] refactor: remove `Dec` type (#24375) --- math/CHANGELOG.md | 12 +- math/dec.go | 578 ---------- math/dec_bench_test.go | 353 ------- math/dec_examples_test.go | 332 ------ math/dec_migrate.go | 8 - math/dec_migrate_test.go | 118 --- math/dec_rapid_test.go | 527 --------- math/dec_test.go | 1937 ---------------------------------- math/go.mod | 17 +- math/go.sum | 33 +- math/legacy_dec_fuzz_test.go | 68 +- math/legacy_dec_test.go | 104 ++ 12 files changed, 192 insertions(+), 3895 deletions(-) delete mode 100644 math/dec.go delete mode 100644 math/dec_bench_test.go delete mode 100644 math/dec_examples_test.go delete mode 100644 math/dec_migrate.go delete mode 100644 math/dec_migrate_test.go delete mode 100644 math/dec_rapid_test.go delete mode 100644 math/dec_test.go diff --git a/math/CHANGELOG.md b/math/CHANGELOG.md index 23c86a8074..ade5d843fc 100644 --- a/math/CHANGELOG.md +++ b/math/CHANGELOG.md @@ -36,18 +36,22 @@ Ref: https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.j ## [Unreleased] -## [math/v1.5.2](https://github.com/cosmos/cosmos-sdk/releases/tag/math/v1.5.2) - 2025-03-31 +## [math/v1.5.3](https://github.com/cosmos/cosmos-sdk/releases/tag/math/v1.5.3) - 2025-04-04 + +* [#24375](https://github.com/cosmos/cosmos-sdk/pull/24375) Remove GDA decimal type. NOTE: the previous v1.5.x family releases have been retracted as they were released with broken features. This release sets `math` to support everything in the `v1.4.x` family with testing improvements and dependency bumps + +## [RETRACTED math/v1.5.2](https://github.com/cosmos/cosmos-sdk/releases/tag/math/v1.5.2) - 2025-03-31 ### Features * [#24343](https://github.com/cosmos/cosmos-sdk/pull/24343) feat(math/Dec): Add comparison and utility APIs MinDec, MaxDec, Neg(), Abs(), GT(), GTE(), LT(), LTE(). * [#24229](https://github.com/cosmos/cosmos-sdk/pull/24229) Add `DecFromLegacyDec` migration function. - -## [math/v1.5.1](https://github.com/cosmos/cosmos-sdk/releases/tag/math/v1.5.1) - 2025-03-28 + +## [RETRACTED math/v1.5.1](https://github.com/cosmos/cosmos-sdk/releases/tag/math/v1.5.1) - 2025-03-28 * [#24185](https://github.com/cosmos/cosmos-sdk/issues/24185) Minor dependency bumps -## [math/v1.5.0](https://github.com/cosmos/cosmos-sdk/releases/tag/math/v1.5.0) - 2025-01-06 +## [RETRACTED 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 diff --git a/math/dec.go b/math/dec.go deleted file mode 100644 index 32a49c1af7..0000000000 --- a/math/dec.go +++ /dev/null @@ -1,578 +0,0 @@ -package math - -import ( - "encoding/json" - stderrors "errors" - "math/big" - "strconv" - - "github.com/cockroachdb/apd/v3" - - "cosmossdk.io/errors" -) - -var _ customProtobufType = &Dec{} - -const ( - // 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 ( - ErrInvalidDec = errors.Register(mathCodespace, 1, "invalid decimal") - ErrUnexpectedRounding = errors.Register(mathCodespace, 2, "unexpected rounding") - ErrNonIntegral = errors.Register(mathCodespace, 3, "value is non-integral") -) - -// 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, -} - -// 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". -// -// Examples: -// - "123" -> Dec{123} -// - "-123.456" -> Dec{-123.456} -// - "1.23E4" -> Dec{12300} -// - "NaN" or "Infinity" -> ErrInvalidDec -// -// 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 -// -// 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()) - } - - 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()) - } - - return z, nil -} - -// 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 - } - 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()) - } - - 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 -} - -// Neg returns a new Dec with value `-x` (negation of x) without mutating the argument x. -// It returns an error if the negation operation fails. -func (x Dec) Neg() (Dec, error) { - var z Dec - if _, err := dec128Context.Neg(&z.dec, &x.dec); err != nil { - return z, ErrInvalidDec.Wrap(err.Error()) - } - return z, nil -} - -// Abs returns a new Dec with the absolute value of x without mutating the argument x. -// It returns an error if the absolute value operation fails. -func (x Dec) Abs() (Dec, error) { - var z Dec - if _, err := dec128Context.Abs(&z.dec, &x.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 - } - - return z, nil -} - -// 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 nil, ErrNonIntegral - } - return z, nil -} - -// 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 { - decs.Exp(decs, apd.NewBigInt(int64(-y.dec.Exponent)), nil) - r.Quo(&y.dec.Coeff, decs) - } - } - if x.dec.Negative { - r.Neg(&r) - } - bigInt := r.MathBigInt() - if bigInt.BitLen() > MaxBitLen { - return ZeroInt(), ErrNonIntegral - } - return NewIntFromBigInt(bigInt), nil -} - -// String formatted in decimal notation: '-ddddd.dddd', no exponent -func (x Dec) String() string { - return string(fmtE(x.dec, 'E')) -} - -// Text converts the floating-point number x to a string according -// to the given format. The format is one of: -// -// '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) -} - -// LT (less than) returns true if x is less than y, false otherwise -func (x Dec) LT(y Dec) bool { - return x.Cmp(y) == -1 -} - -// LTE (less than or equal) returns true if x is less than or equal to y, false otherwise -func (x Dec) LTE(y Dec) bool { - return x.Cmp(y) != 1 -} - -// GT (greater than) returns true if x is greater than y, false otherwise -func (x Dec) GT(y Dec) bool { - return x.Cmp(y) == 1 -} - -// GTE (greater than or equal) returns true if x is greater than or equal to y, false otherwise -func (x Dec) GTE(y Dec) bool { - return x.Cmp(y) != -1 -} - -// 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:]...) - } - - 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) -} - -// 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 ErrInvalidDec.Wrap(err.Error()) - } - - if result.dec.Form != apd.Finite { - return ErrInvalidDec.Wrap("unknown decimal form") - } - - x.dec = result.dec - return nil -} - -// 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 - } - - return copy(data, bz), nil -} - -// 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) -} - -// 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')) -} - -// 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 - } - val, err := NewDecFromString(text) - if err != nil { - return err - } - *x = val - return nil -} - -// MinDec returns the smaller of x and y -func MinDec(x, y Dec) Dec { - if x.LT(y) { - return x - } - return y -} - -// MaxDec returns the larger of x and y -func MaxDec(x, y Dec) Dec { - if x.GT(y) { - return x - } - return y -} diff --git a/math/dec_bench_test.go b/math/dec_bench_test.go deleted file mode 100644 index 52d79c8b44..0000000000 --- a/math/dec_bench_test.go +++ /dev/null @@ -1,353 +0,0 @@ -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 deleted file mode 100644 index 0d5cb29cd2..0000000000 --- a/math/dec_examples_test.go +++ /dev/null @@ -1,332 +0,0 @@ -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+100000 + 1 - if err != nil { - fmt.Println(err.Error()) - } - - sum, err = NewDecWithExp(1, maxExp).Add(NewDecWithExp(1, maxExp)) // 1E+100000 + 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_migrate.go b/math/dec_migrate.go deleted file mode 100644 index 9a855a4c9c..0000000000 --- a/math/dec_migrate.go +++ /dev/null @@ -1,8 +0,0 @@ -package math - -// DecFromLegacyDec converts a LegacyDec to the Dec type using a string intermediate representation. -// -// This function can be used when migrating LegacyDec types to the Dec type. -func DecFromLegacyDec(legacyDec LegacyDec) (Dec, error) { - return NewDecFromString(legacyDec.String()) -} diff --git a/math/dec_migrate_test.go b/math/dec_migrate_test.go deleted file mode 100644 index 5de3c47707..0000000000 --- a/math/dec_migrate_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package math_test - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "cosmossdk.io/math" -) - -// TestDecFromLegacyDec verifies that converting a LegacyDec to a Dec via string round-trip works as expected. -func TestDecFromLegacyDec(t *testing.T) { - // Define test cases: a list of valid decimal string representations. - // Note: The legacy format always prints exactly 18 decimal places. - testCases := []struct { - name string - inputStr string - }{ - {"Zero", "0"}, - {"One", "1"}, - {"NegativeOne", "-1"}, - {"IntegerWithNoDecimals", "123456789012345678"}, - {"SimpleDecimal", "123.456"}, - {"NegativeDecimal", "-9876.543210"}, - {"SmallestUnit", "0.000000000000000001"}, // 10^-18 - {"LargeNumber", "12345678901234567890.123456789012345678"}, - {"TrailingZeros", "100.000000000000000000"}, - } - - for _, tc := range testCases { - // capture range variable - t.Run(tc.name, func(t *testing.T) { - // Create a LegacyDec from the test input string. - legacyDec, err := math.LegacyNewDecFromStr(tc.inputStr) - require.NoError(t, err) - - // Convert using our conversion function. - dec, err := math.DecFromLegacyDec(legacyDec) - require.NoError(t, err) - - // Convert directly from the input string for a canonical value. - expectedDec, err := math.NewDecFromString(tc.inputStr) - require.NoError(t, err) - - // Compare the two Dec values. - require.True(t, dec.Equal(expectedDec)) - }) - } -} - -func TestDecFromLegacyDecDeterministic(t *testing.T) { - // List of test input strings in legacy format. - testInputs := []string{ - "0", - "1", - "-1", - "123.456", - "-9876.543210", - "0.000000000000000001", - "100.000000000000000000", - "12345678901234567890.123456789012345678", - } - - // For each input, convert the legacy string to LegacyDec and then run conversion multiple times. - for _, s := range testInputs { - legacyDec, err := math.LegacyNewDecFromStr(s) - require.NoError(t, err) - - // Run the conversion multiple times. - dec1, err := math.DecFromLegacyDec(legacyDec) - require.NoError(t, err) - - dec2, err := math.DecFromLegacyDec(legacyDec) - require.NoError(t, err) - - dec3, err := math.DecFromLegacyDec(legacyDec) - require.NoError(t, err) - - require.True(t, dec1.Equal(dec2) && dec2.Equal(dec3)) - } -} - -// FuzzDecFromLegacyDec fuzzes the conversion function from LegacyDec to Dec. -func FuzzDecFromLegacyDec(f *testing.F) { - // Seed the fuzzer with some valid input strings. - seedInputs := []string{ - "0", - "1", - "-1", - "123.456", - "-9876.543210", - "0.000000000000000001", - "100.000000000000000000", - "12345678901234567890.123456789012345678", - } - for _, s := range seedInputs { - f.Add(s) - } - - f.Fuzz(func(t *testing.T, inputStr string) { - // Attempt to create a LegacyDec from the fuzz input. - legacyDec, err := math.LegacyNewDecFromStr(inputStr) - if err != nil { - // Ignore inputs that do not form a valid LegacyDec. - return - } - - // Convert using the conversion function. - dec, err := math.DecFromLegacyDec(legacyDec) - require.NoError(t, err) - - // Convert directly from the legacy string output. - expectedDec, err := math.NewDecFromString(legacyDec.String()) - require.NoError(t, err) - - require.True(t, dec.Equal(expectedDec)) - }) -} diff --git a/math/dec_rapid_test.go b/math/dec_rapid_test.go deleted file mode 100644 index 8a1830b866..0000000000 --- a/math/dec_rapid_test.go +++ /dev/null @@ -1,527 +0,0 @@ -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 deleted file mode 100644 index a4c84cef52..0000000000 --- a/math/dec_test.go +++ /dev/null @@ -1,1937 +0,0 @@ -package math - -import ( - "fmt" - "math" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestNewDecFromString(t *testing.T) { - specs := map[string]struct { - src string - exp Dec - expErr error - }{ - "simple decimal": { - src: "1", - exp: NewDecFromInt64(1), - }, - "simple negative decimal": { - src: "-1", - exp: NewDecFromInt64(-1), - }, - "valid decimal with decimal places": { - src: "1.234", - exp: NewDecWithExp(1234, -3), - }, - "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), - }, - - "valid decimal without trailing digits": { - src: "123.", - exp: NewDecWithExp(123, 0), - }, - - "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 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)) - }) - } -} - -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", - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - got := NewDecFromInt64(spec.src) - assert.Equal(t, spec.exp, got.String()) - }) - } -} - -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, - }, - } - 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) - }) - } -} - -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 - 1e-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")), - }, - } - 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()) - }) - } -} - -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, - }, - } - 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())) - }) - } -} - -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) - - 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 / 0 -> 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 / 1e-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) - 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 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)) - }) - } -} - -func TestLT(t *testing.T) { - specs := map[string]struct { - x Dec - y Dec - exp bool - }{ - "0 == 0": { - x: NewDecFromInt64(0), - y: NewDecFromInt64(0), - exp: false, - }, - "0 < 123": { - x: NewDecFromInt64(0), - y: NewDecFromInt64(123), - exp: true, - }, - "123 > 0": { - x: NewDecFromInt64(123), - y: NewDecFromInt64(0), - exp: false, - }, - "-123 < 0": { - x: NewDecFromInt64(-123), - y: NewDecFromInt64(0), - exp: true, - }, - "123 == 123": { - x: NewDecFromInt64(123), - y: NewDecFromInt64(123), - exp: false, - }, - "-123 == -123": { - x: NewDecFromInt64(-123), - y: NewDecFromInt64(-123), - exp: false, - }, - "1.234 == 1.234": { - x: NewDecWithExp(1234, -3), - y: NewDecWithExp(1234, -3), - exp: false, - }, - "1.234 > 1.233": { - x: NewDecWithExp(1234, -3), - y: NewDecWithExp(1233, -3), - exp: false, - }, - "1.233 < 1.234": { - x: NewDecWithExp(1233, -3), - y: NewDecWithExp(1234, -3), - exp: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - got := spec.x.LT(spec.y) - assert.Equal(t, spec.exp, got, "x: %s, y: %s", spec.x.String(), spec.y.String()) - }) - } -} - -func TestLTE(t *testing.T) { - specs := map[string]struct { - x Dec - y Dec - exp bool - }{ - "0 == 0": { - x: NewDecFromInt64(0), - y: NewDecFromInt64(0), - exp: true, - }, - "0 < 123": { - x: NewDecFromInt64(0), - y: NewDecFromInt64(123), - exp: true, - }, - "123 > 0": { - x: NewDecFromInt64(123), - y: NewDecFromInt64(0), - exp: false, - }, - "-123 < 0": { - x: NewDecFromInt64(-123), - y: NewDecFromInt64(0), - exp: true, - }, - "123 == 123": { - x: NewDecFromInt64(123), - y: NewDecFromInt64(123), - exp: true, - }, - "-123 == -123": { - x: NewDecFromInt64(-123), - y: NewDecFromInt64(-123), - exp: true, - }, - "1.234 == 1.234": { - x: NewDecWithExp(1234, -3), - y: NewDecWithExp(1234, -3), - exp: true, - }, - "1.234 > 1.233": { - x: NewDecWithExp(1234, -3), - y: NewDecWithExp(1233, -3), - exp: false, - }, - "1.233 < 1.234": { - x: NewDecWithExp(1233, -3), - y: NewDecWithExp(1234, -3), - exp: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - got := spec.x.LTE(spec.y) - assert.Equal(t, spec.exp, got, "x: %s, y: %s", spec.x.String(), spec.y.String()) - }) - } -} - -func TestGT(t *testing.T) { - specs := map[string]struct { - x Dec - y Dec - exp bool - }{ - "0 == 0": { - x: NewDecFromInt64(0), - y: NewDecFromInt64(0), - exp: false, - }, - "0 < 123": { - x: NewDecFromInt64(0), - y: NewDecFromInt64(123), - exp: false, - }, - "123 > 0": { - x: NewDecFromInt64(123), - y: NewDecFromInt64(0), - exp: true, - }, - "-123 < 0": { - x: NewDecFromInt64(-123), - y: NewDecFromInt64(0), - exp: false, - }, - "123 == 123": { - x: NewDecFromInt64(123), - y: NewDecFromInt64(123), - exp: false, - }, - "-123 == -123": { - x: NewDecFromInt64(-123), - y: NewDecFromInt64(-123), - exp: false, - }, - "1.234 == 1.234": { - x: NewDecWithExp(1234, -3), - y: NewDecWithExp(1234, -3), - exp: false, - }, - "1.234 > 1.233": { - x: NewDecWithExp(1234, -3), - y: NewDecWithExp(1233, -3), - exp: true, - }, - "1.233 < 1.234": { - x: NewDecWithExp(1233, -3), - y: NewDecWithExp(1234, -3), - exp: false, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - got := spec.x.GT(spec.y) - assert.Equal(t, spec.exp, got, "x: %s, y: %s", spec.x.String(), spec.y.String()) - }) - } -} - -func TestGTE(t *testing.T) { - specs := map[string]struct { - x Dec - y Dec - exp bool - }{ - "0 == 0": { - x: NewDecFromInt64(0), - y: NewDecFromInt64(0), - exp: true, - }, - "0 < 123": { - x: NewDecFromInt64(0), - y: NewDecFromInt64(123), - exp: false, - }, - "123 > 0": { - x: NewDecFromInt64(123), - y: NewDecFromInt64(0), - exp: true, - }, - "-123 < 0": { - x: NewDecFromInt64(-123), - y: NewDecFromInt64(0), - exp: false, - }, - "123 == 123": { - x: NewDecFromInt64(123), - y: NewDecFromInt64(123), - exp: true, - }, - "-123 == -123": { - x: NewDecFromInt64(-123), - y: NewDecFromInt64(-123), - exp: true, - }, - "1.234 == 1.234": { - x: NewDecWithExp(1234, -3), - y: NewDecWithExp(1234, -3), - exp: true, - }, - "1.234 > 1.233": { - x: NewDecWithExp(1234, -3), - y: NewDecWithExp(1233, -3), - exp: true, - }, - "1.233 < 1.234": { - x: NewDecWithExp(1233, -3), - y: NewDecWithExp(1234, -3), - exp: false, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - got := spec.x.GTE(spec.y) - assert.Equal(t, spec.exp, got, "x: %s, y: %s", spec.x.String(), spec.y.String()) - }) - } -} - -func TestMinDec(t *testing.T) { - specs := map[string]struct { - x Dec - y Dec - exp Dec - }{ - "0 == 0": { - x: NewDecFromInt64(0), - y: NewDecFromInt64(0), - exp: NewDecFromInt64(0), - }, - "0 < 123": { - x: NewDecFromInt64(0), - y: NewDecFromInt64(123), - exp: NewDecFromInt64(0), - }, - "123 > 0": { - x: NewDecFromInt64(123), - y: NewDecFromInt64(0), - exp: NewDecFromInt64(0), - }, - "-123 < 0": { - x: NewDecFromInt64(-123), - y: NewDecFromInt64(0), - exp: NewDecFromInt64(-123), - }, - "123 == 123": { - x: NewDecFromInt64(123), - y: NewDecFromInt64(123), - exp: NewDecFromInt64(123), - }, - "-123 == -123": { - x: NewDecFromInt64(-123), - y: NewDecFromInt64(-123), - exp: NewDecFromInt64(-123), - }, - "1.234 == 1.234": { - x: NewDecWithExp(1234, -3), - y: NewDecWithExp(1234, -3), - exp: NewDecWithExp(1234, -3), - }, - "1.234 > 1.233": { - x: NewDecWithExp(1234, -3), - y: NewDecWithExp(1233, -3), - exp: NewDecWithExp(1233, -3), - }, - "1.233 < 1.234": { - x: NewDecWithExp(1233, -3), - y: NewDecWithExp(1234, -3), - exp: NewDecWithExp(1233, -3), - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - got := MinDec(spec.x, spec.y) - assert.Equal(t, spec.exp, got, "x: %s, y: %s", spec.x.String(), spec.y.String()) - }) - } -} - -func TestMaxDec(t *testing.T) { - specs := map[string]struct { - x Dec - y Dec - exp Dec - }{ - "0 == 0": { - x: NewDecFromInt64(0), - y: NewDecFromInt64(0), - exp: NewDecFromInt64(0), - }, - "0 < 123 ": { - x: NewDecFromInt64(0), - y: NewDecFromInt64(123), - exp: NewDecFromInt64(123), - }, - "123 > 0 ": { - x: NewDecFromInt64(123), - y: NewDecFromInt64(0), - exp: NewDecFromInt64(123), - }, - "-123 < 0": { - x: NewDecFromInt64(-123), - y: NewDecFromInt64(0), - exp: NewDecFromInt64(0), - }, - "123 == 123": { - x: NewDecFromInt64(123), - y: NewDecFromInt64(123), - exp: NewDecFromInt64(123), - }, - "-123 == -123": { - x: NewDecFromInt64(-123), - y: NewDecFromInt64(-123), - exp: NewDecFromInt64(-123), - }, - "1.234 == 1.234": { - x: NewDecWithExp(1234, -3), - y: NewDecWithExp(1234, -3), - exp: NewDecWithExp(1234, -3), - }, - "1.234 > 1.233": { - x: NewDecWithExp(1234, -3), - y: NewDecWithExp(1233, -3), - exp: NewDecWithExp(1234, -3), - }, - "1.233 < 1.234": { - x: NewDecWithExp(1233, -3), - y: NewDecWithExp(1234, -3), - exp: NewDecWithExp(1234, -3), - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - got := MaxDec(spec.x, spec.y) - assert.Equal(t, spec.exp, got, "x: %s, y: %s", spec.x.String(), spec.y.String()) - }) - } -} - -func TestNeg(t *testing.T) { - specs := map[string]struct { - x Dec - exp Dec - expErr error - }{ - "0": { - x: NewDecFromInt64(0), - exp: NewDecFromInt64(0), - }, - "123": { - x: NewDecFromInt64(123), - exp: NewDecFromInt64(-123), - }, - "-123": { - x: NewDecFromInt64(-123), - exp: NewDecFromInt64(123), - }, - "1.234": { - x: NewDecWithExp(1234, -3), - exp: NewDecWithExp(-1234, -3), - }, - "-1.234 ": { - x: NewDecWithExp(-1234, -3), - exp: NewDecWithExp(1234, -3), - }, - - "1e100000": { - x: NewDecWithExp(1, 100_000), - exp: NewDecWithExp(-1, 100_000), - }, - "-9e900000 -> Err": { - x: NewDecWithExp(-9, 900_000), - expErr: ErrInvalidDec, - }, - "-1e^-1": { - x: NewDecWithExp(-1, -1), - exp: NewDecWithExp(1, -1), - }, - "-1e100001": { - x: NewDecWithExp(-1, 100_001), - expErr: ErrInvalidDec, - }, - "1e100001": { - x: NewDecWithExp(1, 100_001), - expErr: ErrInvalidDec, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - got, gotErr := spec.x.Neg() - if spec.expErr != nil { - require.ErrorIs(t, gotErr, spec.expErr, got) - return - } - require.NoError(t, gotErr) - assert.Equal(t, spec.exp, got) - }) - } -} - -func TestAbs(t *testing.T) { - specs := map[string]struct { - x Dec - exp Dec - expErr error - }{ - "0": { - x: NewDecFromInt64(0), - exp: NewDecFromInt64(0), - }, - "123": { - x: NewDecFromInt64(123), - exp: NewDecFromInt64(123), - }, - "-123": { - x: NewDecFromInt64(-123), - exp: NewDecFromInt64(123), - }, - "1.234": { - x: NewDecWithExp(1234, -3), - exp: NewDecWithExp(1234, -3), - }, - "-1.234 ": { - x: NewDecWithExp(-1234, -3), - exp: NewDecWithExp(1234, -3), - }, - - "1e100000": { - x: NewDecWithExp(1, 100_000), - exp: NewDecWithExp(1, 100_000), - }, - "-9e900000 -> Err": { - x: NewDecWithExp(-9, 900_000), - expErr: ErrInvalidDec, - }, - "-1e^-1": { - x: NewDecWithExp(-1, -1), - exp: NewDecWithExp(1, -1), - }, - "-1e100001": { - x: NewDecWithExp(-1, 100_001), - expErr: ErrInvalidDec, - }, - "1e100001": { - x: NewDecWithExp(1, 100_001), - expErr: ErrInvalidDec, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - got, gotErr := spec.x.Abs() - if spec.expErr != nil { - require.ErrorIs(t, gotErr, spec.expErr, got) - return - } - require.NoError(t, gotErr) - assert.Equal(t, spec.exp, got) - }) - } -} diff --git a/math/go.mod b/math/go.mod index 5c76551fe6..840e95b750 100644 --- a/math/go.mod +++ b/math/go.mod @@ -3,26 +3,23 @@ module cosmossdk.io/math go 1.23.0 require ( - cosmossdk.io/errors v1.0.2 - github.com/cockroachdb/apd/v3 v3.2.1 github.com/stretchr/testify v1.10.0 - pgregory.net/rapid v1.2.0 sigs.k8s.io/yaml v1.4.0 ) require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/kr/text v0.2.0 // indirect - github.com/pkg/errors v0.9.1 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - golang.org/x/net v0.35.0 // indirect - golang.org/x/sys v0.31.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect - google.golang.org/grpc v1.71.1 // indirect - google.golang.org/protobuf v1.36.6 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) +// reverted the broken Dec type +retract [v1.5.0, v1.5.2] + // Issue with math.Int{}.Size() implementation. retract [v1.1.0, v1.1.1] diff --git a/math/go.sum b/math/go.sum index f92d5a06c3..5251fa3452 100644 --- a/math/go.sum +++ b/math/go.sum @@ -1,47 +1,28 @@ -cosmossdk.io/errors v1.0.2 h1:wcYiJz08HThbWxd/L4jObeLaLySopyyuUFB5w4AGpCo= -cosmossdk.io/errors v1.0.2/go.mod h1:0rjgiHkftRYPj//3DrD6y8hcm40HcPv/dR4R/4efr0k= -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.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/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 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/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= 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.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= -google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= -google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= -pgregory.net/rapid v1.2.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/legacy_dec_fuzz_test.go b/math/legacy_dec_fuzz_test.go index e50ae41bfa..8147a656b4 100644 --- a/math/legacy_dec_fuzz_test.go +++ b/math/legacy_dec_fuzz_test.go @@ -1,7 +1,10 @@ package math import ( + "fmt" "testing" + + "github.com/stretchr/testify/require" ) func FuzzLegacyNewDecFromStr(f *testing.F) { @@ -17,8 +20,69 @@ func FuzzLegacyNewDecFromStr(f *testing.F) { f.Fuzz(func(t *testing.T, input string) { dec, err := LegacyNewDecFromStr(input) - if err != nil && !dec.IsNil() { - t.Fatalf("Inconsistency: dec.notNil=%v yet err=%v", dec, err) + require.NoError(t, err) + require.True(t, !dec.IsNil()) + }) +} + +func FuzzLegacyDecMarshalUnmarshalJSON(f *testing.F) { + // Seed with some valid decimal strings. + seeds := []string{ + "0", "123", "-123", "123.456", "-123.456", "1.23E4", "1.23e4", "1.23456789E-10", + } + for _, seed := range seeds { + f.Add(seed) + } + + f.Fuzz(func(t *testing.T, input string) { + // Try to create a LegacyDec from the input. + dec, err := LegacyNewDecFromStr(input) + if err != nil { + // Skip inputs that cannot be parsed. + t.Skip() } + + // Marshal to JSON. + jsonData, err := dec.MarshalJSON() + require.NoError(t, err) + + // Unmarshal back. + var decoded LegacyDec + err = decoded.UnmarshalJSON(jsonData) + require.NoError(t, err) + + // Check that the round-trip value is equal. + require.True(t, dec.Equal(decoded), fmt.Sprintf("JSON round-trip mismatch for input %q: original %q, decoded %q", input, dec.String(), decoded.String())) + }) +} + +func FuzzLegacyDecMarshalUnmarshal(f *testing.F) { + // Seed with some valid decimal strings. + seeds := []string{ + "0", "123", "-123", "123.456", "-123.456", "1.23E4", "1.23e4", "1.23456789E-10", + } + for _, seed := range seeds { + f.Add(seed) + } + + f.Fuzz(func(t *testing.T, input string) { + // Parse the input into a LegacyDec. + dec, err := LegacyNewDecFromStr(input) + if err != nil { + // Skip invalid inputs. + t.Skip() + } + + // Marshal using the custom binary (gogo proto) encoding. + bz, err := dec.Marshal() + require.NoError(t, err) + + // Unmarshal back. + var decoded LegacyDec + err = decoded.Unmarshal(bz) + require.NoError(t, err) + + // Check that the round-trip value is equal. + require.True(t, dec.Equal(decoded), fmt.Sprintf("JSON round-trip mismatch for input %q: original %q, decoded %q", input, dec.String(), decoded.String())) }) } diff --git a/math/legacy_dec_test.go b/math/legacy_dec_test.go index 1f0b288a08..a24fdd88d6 100644 --- a/math/legacy_dec_test.go +++ b/math/legacy_dec_test.go @@ -1326,3 +1326,107 @@ func BenchmarkIsInValidRange(b *testing.B) { }) } } + +func TestFormatDecValid(t *testing.T) { + tests := []struct { + input string + expected string + }{ + {"123", "123"}, + {"123.456000", "123.456"}, + {"-123.450", "-123.45"}, + {"00123.4500", "123.45"}, + {"0.000000000000000000", "0"}, + {"123.000000000000000000", "123"}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + formatted, err := math.FormatDec(tt.input) + require.NoError(t, err, "FormatDec(%q) should not error", tt.input) + require.Equal(t, tt.expected, formatted, "unexpected formatted value for input %q", tt.input) + }) + } +} + +func TestRoundAndTruncate(t *testing.T) { + // These cases use bankers rounding: + // For example, 0.25 rounds to 0, 0.75 rounds to 1, 1.5 rounds to 2, while truncation always drops the decimal. + tests := []struct { + input string + expectedRoundInt int64 + expectedTruncate int64 + }{ + {"0.25", 0, 0}, + {"0.75", 1, 0}, + {"1.5", 2, 1}, + {"2.5", 2, 2}, + {"7.5", 8, 7}, + {"-0.25", 0, 0}, + {"-0.75", -1, 0}, + {"-1.5", -2, -1}, + {"-2.5", -2, -2}, + {"-7.5", -8, -7}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + dec, err := math.LegacyNewDecFromStr(tt.input) + require.NoError(t, err) + round := dec.RoundInt64() + trunc := dec.TruncateInt64() + require.Equal(t, tt.expectedRoundInt, round, "RoundInt64 mismatch for input %q", tt.input) + require.Equal(t, tt.expectedTruncate, trunc, "TruncateInt64 mismatch for input %q", tt.input) + }) + } +} + +func TestLegacySortableDecBytes(t *testing.T) { + tests := []struct { + input string + expected string // expected string representation of the sortable bytes + }{ + // Note: The expected outputs here are based on the formatting defined in LegacySortableDecBytes. + {"0", "000000000000000000.000000000000000000"}, + {"1", "000000000000000001.000000000000000000"}, + {"10", "000000000000000010.000000000000000000"}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + dec, err := math.LegacyNewDecFromStr(tt.input) + require.NoError(t, err) + bs := math.LegacySortableDecBytes(dec) + require.Equal(t, tt.expected, string(bs), "SortableDecBytes mismatch for input %q", tt.input) + }) + } + + // Test that an out-of-bound decimal causes a panic. + // For example, multiply the max sortable decimal by 2. + outOfBound := math.LegacyMaxSortableDec.Mul(math.LegacyNewDec(2)) + require.Panics(t, func() { + _ = math.LegacySortableDecBytes(outOfBound) + }, "expected panic for out-of-bound decimal") +} + +func TestLegacyNewDecFromStr_TooManyDecimals(t *testing.T) { + // Create a decimal string with more than LegacyPrecision digits after the dot. + decStr := "1." + strings.Repeat("1", math.LegacyPrecision+1) + _, err := math.LegacyNewDecFromStr(decStr) + require.Error(t, err, "expected error when input has more than %d decimal places", math.LegacyPrecision) +} + +func TestUnmarshalJSON_InvalidFormat(t *testing.T) { + var dec math.LegacyDec + // Passing a JSON number rather than a string should error. + err := dec.UnmarshalJSON([]byte("123")) + require.Error(t, err, "expected error when unmarshaling a non-string JSON value") +} + +func TestMarshalYAML(t *testing.T) { + dec, err := math.LegacyNewDecFromStr("123.456") + require.NoError(t, err) + y, err := dec.MarshalYAML() + require.NoError(t, err) + require.Equal(t, dec.String(), y, "YAML marshaling mismatch") +}