feat(math/Dec): Add comparison and utility APIs MinDec, MaxDec, Neg(), Abs(), GT(), GTE(), LT(), LTE() (#24343)

This commit is contained in:
Đông Liều 2025-04-03 00:28:10 +07:00 committed by GitHub
parent 11c41291a4
commit 41e3e9d004
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 539 additions and 0 deletions

View File

@ -40,6 +40,7 @@ Ref: https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.j
### 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

View File

@ -254,6 +254,26 @@ func (x Dec) Mul(y Dec) (Dec, 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.
//
@ -365,6 +385,26 @@ 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 {
@ -520,3 +560,19 @@ func (x *Dec) UnmarshalJSON(data []byte) error {
*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
}

View File

@ -1453,3 +1453,485 @@ func TestMarshalUnmarshal(t *testing.T) {
})
}
}
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)
})
}
}