diff --git a/math/dec.go b/math/dec.go index ffcc67a4d48a..19e29170243d 100644 --- a/math/dec.go +++ b/math/dec.go @@ -27,7 +27,7 @@ const ( const mathCodespace = "math" var ( - ErrInvalidDecString = errors.Register(mathCodespace, 1, "invalid decimal string") + ErrInvalidDec = errors.Register(mathCodespace, 1, "invalid decimal string") ErrUnexpectedRounding = errors.Register(mathCodespace, 2, "unexpected rounding") ErrNonIntegeral = errors.Register(mathCodespace, 3, "value is non-integral") ) @@ -49,7 +49,7 @@ type SetupConstraint func(Dec) error func AssertNotNegative() SetupConstraint { return func(d Dec) error { if d.IsNegative() { - return ErrInvalidDecString.Wrap("is negative") + return ErrInvalidDec.Wrap("is negative") } return nil } @@ -59,7 +59,7 @@ func AssertNotNegative() SetupConstraint { func AssertGreaterThanZero() SetupConstraint { return func(d Dec) error { if !d.IsPositive() { - return ErrInvalidDecString.Wrap("is negative") + return ErrInvalidDec.Wrap("is negative") } return nil } @@ -69,7 +69,7 @@ func AssertGreaterThanZero() SetupConstraint { func AssertMaxDecimals(max uint32) SetupConstraint { return func(d Dec) error { if d.NumDecimalPlaces() > max { - return ErrInvalidDecString.Wrapf("exceeds maximum decimal places: %d", max) + return ErrInvalidDec.Wrapf("exceeds maximum decimal places: %d", max) } return nil } @@ -79,14 +79,14 @@ func AssertMaxDecimals(max uint32) SetupConstraint { func NewDecFromString(s string, c ...SetupConstraint) (Dec, error) { d, _, err := apd.NewFromString(s) if err != nil { - return Dec{}, ErrInvalidDecString.Wrap(err.Error()) + return Dec{}, ErrInvalidDec.Wrap(err.Error()) } switch d.Form { case apd.NaN, apd.NaNSignaling: - return Dec{}, ErrInvalidDecString.Wrap("not a number") + return Dec{}, ErrInvalidDec.Wrap("not a number") case apd.Infinite: - return Dec{}, ErrInvalidDecString.Wrapf(s) + return Dec{}, ErrInvalidDec.Wrapf(s) default: result := Dec{*d} for _, v := range c { @@ -117,7 +117,7 @@ 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{}, ErrInvalidDecString.Wrap(err.Error()) + return Dec{}, ErrInvalidDec.Wrap(err.Error()) } return z, nil @@ -128,8 +128,9 @@ func (x Dec) Add(y Dec) (Dec, error) { func (x Dec) Sub(y Dec) (Dec, error) { var z Dec _, err := apd.BaseContext.Sub(&z.dec, &x.dec, &y.dec) + if err != nil { - return Dec{}, ErrInvalidDecString + return Dec{}, ErrInvalidDec.Wrap(err.Error()) } return z, errors.Wrap(err, "decimal subtraction error") @@ -141,7 +142,7 @@ 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{}, ErrInvalidDecString + return Dec{}, ErrInvalidDec } return z, errors.Wrap(err, "decimal quotient error") @@ -167,7 +168,7 @@ 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, ErrInvalidDecString + return z, ErrInvalidDec } if condition.Rounded() { return z, ErrUnexpectedRounding @@ -181,7 +182,7 @@ 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, ErrInvalidDecString + return z, ErrInvalidDec } return z, nil } diff --git a/math/dec_test.go b/math/dec_test.go index 2cade165834f..9fcdd47cfa58 100644 --- a/math/dec_test.go +++ b/math/dec_test.go @@ -2,6 +2,8 @@ package math import ( "fmt" + "math" + "strconv" "strings" "testing" @@ -42,15 +44,15 @@ func TestNewDecFromString(t *testing.T) { }, "precision too high": { src: "." + strings.Repeat("9", 35), - expErr: ErrInvalidDecString, + expErr: ErrInvalidDec, }, "decimal too big": { // todo: src: strings.Repeat("9", 35), // 10^100000+10 - expErr: ErrInvalidDecString, + expErr: ErrInvalidDec, }, "decimal too small": { src: strings.Repeat("9", 35), // -10^100000+0.99999999999999999... +1 - expErr: ErrInvalidDecString, + expErr: ErrInvalidDec, }, "valid decimal with leading zero": { src: "01234", @@ -87,27 +89,27 @@ func TestNewDecFromString(t *testing.T) { "with setup constraint": { src: "-1", constraints: []SetupConstraint{AssertNotNegative()}, - expErr: ErrInvalidDecString, + expErr: ErrInvalidDec, }, "empty string": { src: "", - expErr: ErrInvalidDecString, + expErr: ErrInvalidDec, }, "NaN": { src: "NaN", - expErr: ErrInvalidDecString, + expErr: ErrInvalidDec, }, "random string": { src: "1foo", - expErr: ErrInvalidDecString, + expErr: ErrInvalidDec, }, "Infinity": { src: "Infinity", - expErr: ErrInvalidDecString, + expErr: ErrInvalidDec, }, "Inf": { src: "Inf", - expErr: ErrInvalidDecString, + expErr: ErrInvalidDec, }, } for name, spec := range specs { @@ -142,8 +144,8 @@ func TestNewDecFromInt64(t *testing.T) { exp: "-123", }, "max value": { - src: 9223372036854775807, - exp: "9223372036854775807", + src: math.MaxInt32, + exp: strconv.Itoa(math.MaxInt32), }, "min value": { src: -9223372036854775808, @@ -238,10 +240,11 @@ func TestAdd(t *testing.T) { // x: NewDecWithPrec(1, 36), // y: NewDecWithPrec(1, 36), // constraints: []SetupConstraint{AssertMaxDecimals(34)}, - // expErr: ErrInvalidDecString, + // expErr: ErrInvalidDec, // }, // TO DO: more edge cases For example: 1^100000 + 9^100000 , 1^100000 + 1^-1 + } for name, spec := range specs { t.Run(name, func(t *testing.T) { @@ -258,96 +261,137 @@ func TestAdd(t *testing.T) { func TestSub(t *testing.T) { specs := map[string]struct { - x Dec - y Dec - exp Dec - expErr error + x Dec + y Dec + exp Dec + expErr error + constraints []SetupConstraint }{ - "zero minus zero": { - // 0 - 0 = 0 + "0 - 0 = 0": { x: NewDecFromInt64(0), y: NewDecFromInt64(0), exp: NewDecFromInt64(0), }, - "zero minus simple positive": { - // 0 - 123 = -123 + "0 - 123 = -123": { x: NewDecFromInt64(0), y: NewDecFromInt64(123), exp: NewDecFromInt64(-123), }, - "zero minus simple negative": { - // 0 - -123 = 123 + "0 - -123 = 123": { x: NewDecFromInt64(0), y: NewDecFromInt64(-123), exp: NewDecFromInt64(123), }, - "simple positive minus simple positive": { - // 123 - 123 = 0 + "123 - 123 = 0": { x: NewDecFromInt64(123), y: NewDecFromInt64(123), exp: NewDecFromInt64(0), }, - "simple negative minus simple positive": { - // -123 - 123 = 0 + "-123 - 123 = -246": { x: NewDecFromInt64(-123), y: NewDecFromInt64(123), exp: NewDecFromInt64(-246), }, - "simple negative minus simple negative": { - // -123 - -123 = 0 + "-123 - -123 = 0": { x: NewDecFromInt64(-123), y: NewDecFromInt64(-123), exp: NewDecFromInt64(0), }, - "valid decimal with decimal places add valid decimal with decimal places": { - // 1.234 - 1.234 = 0.000 + "1.234 - 1.234 = 0.000": { x: NewDecWithPrec(1234, -3), y: NewDecWithPrec(1234, -3), exp: NewDecWithPrec(0, -3), }, - "valid decimal with decimal places minus simple positive": { - // 1.234 - 123 = -121.766 + "1.234 - 123 = -121.766": { x: NewDecWithPrec(1234, -3), y: NewDecFromInt64(123), exp: NewDecWithPrec(-121766, -3), }, - "valid decimal with decimal places minus simple negative": { - // 1.234 - -123 = 1.111 + "1.234 - -123 = 1.111": { x: NewDecWithPrec(1234, -3), y: NewDecFromInt64(-123), exp: NewDecWithPrec(124234, -3), }, - "valid decimal with decimal places minus valid negative decimal with decimal places": { - // 1.234 - -1.234 = 2.468 + "1.234 - -1.234 = 2.468": { x: NewDecWithPrec(1234, -3), y: NewDecWithPrec(-1234, -3), exp: NewDecWithPrec(2468, -3), }, - "valid negative decimal with decimal places minus valid negative decimal with decimal places": { - // -1.234 - -1.234 = 2.468 + "-1.234 - -1.234 = 2.468": { x: NewDecWithPrec(-1234, -3), y: NewDecWithPrec(-1234, -3), exp: NewDecWithPrec(0, -3), }, - // "precision too high": { - // // 10^34 - 10^34 = 2*10^34 - // x: NewDecWithPrec(1, 36), - // y: NewDecWithPrec(1, 36), - // constraints: []SetupConstraint{AssertMaxDecimals(34)}, - // expErr: ErrInvalidDecString, - // }, + "1 - 0.999 = 0.001 - rounding after comma": { + x: NewDecFromInt64(1), + y: NewDecWithPrec(999, -3), + exp: NewDecWithPrec(1, -3), + }, + "1e100000 - 1^-1 -> Err": { + x: NewDecWithPrec(1, 100_000), + y: NewDecWithPrec(1, -1), + expErr: ErrInvalidDec, + }, + "1e100000 - 1^1-> Err": { + x: NewDecWithPrec(1, 100_000), + y: NewDecWithPrec(1, -1), + expErr: ErrInvalidDec, + }, + "upper exp limit exceeded": { + x: NewDecWithPrec(1, 100_001), + y: NewDecWithPrec(1, 100_001), + expErr: ErrInvalidDec, + }, + "lower exp limit exceeded": { + x: NewDecWithPrec(1, -100_001), + y: NewDecWithPrec(1, -100_001), + expErr: ErrInvalidDec, + }, + "1e100000 - 1 = 999..9": { + x: NewDecWithPrec(1, 100_000), + y: NewDecFromInt64(1), + exp: must(NewDecFromString(strings.Repeat("9", 100_000))), + }, + "1e100000 - 0 = 1e100000": { + x: NewDecWithPrec(1, 100_000), + y: NewDecFromInt64(0), + exp: must(NewDecFromString("1e100000")), + }, + "1e100001 - 0 -> err": { + x: NewDecWithPrec(1, 100_001), + y: NewDecFromInt64(0), + expErr: ErrInvalidDec, + }, + "1e100000 - -1 -> 100..1": { + x: NewDecWithPrec(1, 100_000), + y: NewDecFromInt64(-1), + exp: must(NewDecFromString("1" + strings.Repeat("0", 99_999) + "1")), + }, + "1e-100000 - 0 = 1e-100000": { + x: NewDecWithPrec(1, -100_000), + y: NewDecFromInt64(0), + exp: must(NewDecFromString("1e-100000")), + }, + "1e-100001 - 0 -> err": { + x: NewDecWithPrec(1, -100_001), + y: NewDecFromInt64(0), + expErr: ErrInvalidDec, + }, + "1e-100000 - -1 -> 0.000..01": { + x: NewDecWithPrec(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) - fmt.Println(got) if spec.expErr != nil { - require.ErrorIs(t, gotErr, spec.expErr, got) + require.ErrorIs(t, gotErr, spec.expErr) return } - require.NoError(t, gotErr) - assert.Equal(t, spec.exp, got) + assert.True(t, spec.exp.Equal(got)) }) } } @@ -363,7 +407,7 @@ func TestQuo(t *testing.T) { "0 / 0": { x: NewDecFromInt64(0), y: NewDecFromInt64(0), - expErr: ErrInvalidDecString, + expErr: ErrInvalidDec, }, " 0 / 123 = 0": { x: NewDecFromInt64(0), @@ -379,12 +423,12 @@ func TestQuo(t *testing.T) { "123 / 0 = 0": { x: NewDecFromInt64(123), y: NewDecFromInt64(0), - expErr: ErrInvalidDecString, + expErr: ErrInvalidDec, }, "-123 / 0 = 0": { x: NewDecFromInt64(-123), y: NewDecFromInt64(0), - expErr: ErrInvalidDecString, + expErr: ErrInvalidDec, }, "123 / 123 = 1": { // the answer is showing up as 0 although it should be 1. Again with 34 precision it is mismatched @@ -432,7 +476,7 @@ func TestQuo(t *testing.T) { // x: NewDecWithPrec(1, 36), // y: NewDecWithPrec(1, 36), // constraints: []SetupConstraint{AssertMaxDecimals(34)}, - // expErr: ErrInvalidDecString, + // expErr: ErrInvalidDec, // }, } for name, spec := range specs { @@ -461,7 +505,7 @@ func TestQuoExact(t *testing.T) { "0 / 0": { x: NewDecFromInt64(0), y: NewDecFromInt64(0), - expErr: ErrInvalidDecString, + expErr: ErrInvalidDec, }, " 0 / 123 = 0": { x: NewDecFromInt64(0), @@ -477,12 +521,12 @@ func TestQuoExact(t *testing.T) { "123 / 0 = 0": { x: NewDecFromInt64(123), y: NewDecFromInt64(0), - expErr: ErrInvalidDecString, + expErr: ErrInvalidDec, }, "-123 / 0 = 0": { x: NewDecFromInt64(-123), y: NewDecFromInt64(0), - expErr: ErrInvalidDecString, + expErr: ErrInvalidDec, }, "123 / 123 = 1": { // the answer is showing up as 0 although it should be 1. Again with 34 precision it is mismatched @@ -530,7 +574,7 @@ func TestQuoExact(t *testing.T) { // x: NewDecWithPrec(1, 36), // y: NewDecWithPrec(1, 36), // constraints: []SetupConstraint{AssertMaxDecimals(34)}, - // expErr: ErrInvalidDecString, + // expErr: ErrInvalidDec, // }, } for name, spec := range specs { @@ -559,7 +603,7 @@ func TestQuoInteger(t *testing.T) { "0 / 0": { x: NewDecFromInt64(0), y: NewDecFromInt64(0), - expErr: ErrInvalidDecString, + expErr: ErrInvalidDec, }, " 0 / 123 = 0": { x: NewDecFromInt64(0), @@ -575,12 +619,12 @@ func TestQuoInteger(t *testing.T) { "123 / 0 = 0": { x: NewDecFromInt64(123), y: NewDecFromInt64(0), - expErr: ErrInvalidDecString, + expErr: ErrInvalidDec, }, "-123 / 0 = 0": { x: NewDecFromInt64(-123), y: NewDecFromInt64(0), - expErr: ErrInvalidDecString, + expErr: ErrInvalidDec, }, "123 / 123 = 1": { x: NewDecFromInt64(123), @@ -628,7 +672,7 @@ func TestQuoInteger(t *testing.T) { // x: NewDecWithPrec(1, 36), // y: NewDecWithPrec(1, 36), // constraints: []SetupConstraint{AssertMaxDecimals(34)}, - // expErr: ErrInvalidDecString, + // expErr: ErrInvalidDec, // }, } for name, spec := range specs { @@ -669,31 +713,31 @@ func TestReduce(t *testing.T) { src: "10", exp: "10", decPlaces: 1, - expErr: ErrInvalidDecString, + expErr: ErrInvalidDec, }, "negative value": { src: "-10", exp: "-10", decPlaces: 1, - expErr: ErrInvalidDecString, + expErr: ErrInvalidDec, }, "positive decimal": { src: "1.30000", exp: "1.3", decPlaces: 4, - expErr: ErrInvalidDecString, + expErr: ErrInvalidDec, }, "negative decimal": { src: "-1.30000", exp: "-1.3", decPlaces: 4, - expErr: ErrInvalidDecString, + expErr: ErrInvalidDec, }, "zero decimal and decimal places": { src: "0.00000", exp: "0", decPlaces: 0, - expErr: ErrInvalidDecString, + expErr: ErrInvalidDec, }, } for name, spec := range specs { @@ -839,7 +883,7 @@ func TestToSdkInt(t *testing.T) { func TestInfDecString(t *testing.T) { _, err := NewDecFromString("iNf") require.Error(t, err) - require.ErrorIs(t, err, ErrInvalidDecString) + require.ErrorIs(t, err, ErrInvalidDec) } func must[T any](r T, err error) T {