refactor: remove Dec type (#24375)
This commit is contained in:
+8
-4
@@ -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
|
||||
|
||||
|
||||
-578
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
-1937
File diff suppressed because it is too large
Load Diff
+7
-10
@@ -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]
|
||||
|
||||
|
||||
+7
-26
@@ -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=
|
||||
|
||||
@@ -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()))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user