cosmos-sdk/math/dec_test.go
2024-12-06 14:16:59 +00:00

1456 lines
35 KiB
Go

package math
import (
"fmt"
"math"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewDecFromString(t *testing.T) {
specs := map[string]struct {
src string
exp Dec
expErr error
}{
"simple decimal": {
src: "1",
exp: NewDecFromInt64(1),
},
"simple negative decimal": {
src: "-1",
exp: NewDecFromInt64(-1),
},
"valid decimal with decimal places": {
src: "1.234",
exp: NewDecWithExp(1234, -3),
},
"valid negative decimal": {
src: "-1.234",
exp: NewDecWithExp(-1234, -3),
},
"min decimal": {
src: "-" + strings.Repeat("9", 34),
exp: must(NewDecWithExp(-1, 34).Add(NewDecFromInt64(1))),
},
"max decimal": {
src: strings.Repeat("9", 34),
exp: must(NewDecWithExp(1, 34).Sub(NewDecFromInt64(1))),
},
"too big": {
src: strings.Repeat("9", 100_0000),
expErr: ErrInvalidDec,
},
"too small": {
src: "-" + strings.Repeat("9", 100_0000),
expErr: ErrInvalidDec,
},
"valid decimal with leading zero": {
src: "01234",
exp: NewDecWithExp(1234, 0),
},
"valid decimal without leading zero": {
src: ".1234",
exp: NewDecWithExp(1234, -4),
},
"valid decimal without trailing digits": {
src: "123.",
exp: NewDecWithExp(123, 0),
},
"valid negative decimal without leading zero": {
src: "-.1234",
exp: NewDecWithExp(-1234, -4),
},
"valid negative decimal without trailing digits": {
src: "-123.",
exp: NewDecWithExp(-123, 0),
},
"decimal with scientific notation": {
src: "1.23e4",
exp: NewDecWithExp(123, 2),
},
"decimal with upper case scientific notation": {
src: "1.23E+4",
exp: NewDecWithExp(123, 2),
},
"negative decimal with scientific notation": {
src: "-1.23e4",
exp: NewDecWithExp(-123, 2),
},
"exceed max exp 11E+1000000": {
src: "11E+1000000",
expErr: ErrInvalidDec,
},
"exceed min exp 11E-1000000": {
src: "11E-1000000",
expErr: ErrInvalidDec,
},
"exceed max exp 1E100001": {
src: "1E100001",
expErr: ErrInvalidDec,
},
"exceed min exp 1E-100001": {
src: "1E-100001",
expErr: ErrInvalidDec,
},
"empty string": {
src: "",
expErr: ErrInvalidDec,
},
"NaN": {
src: "NaN",
expErr: ErrInvalidDec,
},
"random string": {
src: "1foo",
expErr: ErrInvalidDec,
},
"Infinity": {
src: "Infinity",
expErr: ErrInvalidDec,
},
"Inf": {
src: "Inf",
expErr: ErrInvalidDec,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
got, gotErr := NewDecFromString(spec.src)
if spec.expErr != nil {
require.ErrorIs(t, gotErr, spec.expErr, got.String())
return
}
require.NoError(t, gotErr)
assert.True(t, spec.exp.Equal(got))
})
}
}
func TestNewDecFromInt64(t *testing.T) {
specs := map[string]struct {
src int64
exp string
}{
"zero value": {
src: 0,
exp: "0",
},
"positive value": {
src: 123,
exp: "123",
},
"negative value": {
src: -123,
exp: "-123",
},
"max value": {
src: math.MaxInt64,
exp: "9223372036854.775807E+6",
},
"min value": {
src: math.MinInt64,
exp: "9223372036854.775808E+6",
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
got := NewDecFromInt64(spec.src)
assert.Equal(t, spec.exp, got.String())
})
}
}
func TestAdd(t *testing.T) {
specs := map[string]struct {
x Dec
y Dec
exp Dec
expErr error
}{
"0 + 0 = 0": {
x: NewDecFromInt64(0),
y: NewDecFromInt64(0),
exp: NewDecFromInt64(0),
},
"0 + 123 = 123": {
x: NewDecFromInt64(0),
y: NewDecFromInt64(123),
exp: NewDecFromInt64(123),
},
"0 + -123 = -123": {
x: NewDecFromInt64(0),
y: NewDecFromInt64(-123),
exp: NewDecFromInt64(-123),
},
"123 + 123 = 246": {
x: NewDecFromInt64(123),
y: NewDecFromInt64(123),
exp: NewDecFromInt64(246),
},
"-123 + 123 = 0": {
x: NewDecFromInt64(-123),
y: NewDecFromInt64(123),
exp: NewDecFromInt64(0),
},
"-123 + -123 = -246": {
x: NewDecFromInt64(-123),
y: NewDecFromInt64(-123),
exp: NewDecFromInt64(-246),
},
"1.234 + 1.234 = 2.468": {
x: NewDecWithExp(1234, -3),
y: NewDecWithExp(1234, -3),
exp: NewDecWithExp(2468, -3),
},
"1.234 + 123 = 124.234": {
x: NewDecWithExp(1234, -3),
y: NewDecFromInt64(123),
exp: NewDecWithExp(124234, -3),
},
"1.234 + -123 = -121.766": {
x: NewDecWithExp(1234, -3),
y: NewDecFromInt64(-123),
exp: must(NewDecFromString("-121.766")),
},
"1.234 + -1.234 = 0": {
x: NewDecWithExp(1234, -3),
y: NewDecWithExp(-1234, -3),
exp: NewDecWithExp(0, -3),
},
"-1.234 + -1.234 = -2.468": {
x: NewDecWithExp(-1234, -3),
y: NewDecWithExp(-1234, -3),
exp: NewDecWithExp(-2468, -3),
},
"1e100000 + 9e900000 -> Err": {
x: NewDecWithExp(1, 100_000),
y: NewDecWithExp(9, 900_000),
expErr: ErrInvalidDec,
},
"1e100000 + -9e900000 -> Err": {
x: NewDecWithExp(1, 100_000),
y: NewDecWithExp(9, 900_000),
expErr: ErrInvalidDec,
},
"1e100000 + 1e^-1 -> err": {
x: NewDecWithExp(1, 100_000),
y: NewDecWithExp(1, -1),
expErr: ErrInvalidDec,
},
"1e100000 + -1e^-1 -> err": {
x: NewDecWithExp(1, 100_000),
y: NewDecWithExp(-1, -1),
expErr: ErrInvalidDec,
},
"1e100000 + 1 -> 100..1": {
x: NewDecWithExp(1, 100_000),
y: NewDecFromInt64(1),
exp: must(NewDecWithExp(1, 100_000).Add(NewDecFromInt64(1))),
},
"1e100001 + 0 -> err": {
x: NewDecWithExp(1, 100_001),
y: NewDecFromInt64(0),
expErr: ErrInvalidDec,
},
"-1e100001 + 0 -> err": {
x: NewDecWithExp(1, -100_001),
y: NewDecFromInt64(0),
expErr: ErrInvalidDec,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
got, gotErr := spec.x.Add(spec.y)
if spec.expErr != nil {
require.ErrorIs(t, gotErr, spec.expErr, got)
return
}
require.NoError(t, gotErr)
assert.Equal(t, spec.exp, got)
})
}
}
func TestSub(t *testing.T) {
specs := map[string]struct {
x Dec
y Dec
exp Dec
expErr error
}{
"0 - 0 = 0": {
x: NewDecFromInt64(0),
y: NewDecFromInt64(0),
exp: NewDecFromInt64(0),
},
"0 - 123 = -123": {
x: NewDecFromInt64(0),
y: NewDecFromInt64(123),
exp: NewDecFromInt64(-123),
},
"0 - -123 = 123": {
x: NewDecFromInt64(0),
y: NewDecFromInt64(-123),
exp: NewDecFromInt64(123),
},
"123 - 123 = 0": {
x: NewDecFromInt64(123),
y: NewDecFromInt64(123),
exp: NewDecFromInt64(0),
},
"-123 - 123 = -246": {
x: NewDecFromInt64(-123),
y: NewDecFromInt64(123),
exp: NewDecFromInt64(-246),
},
"-123 - -123 = 0": {
x: NewDecFromInt64(-123),
y: NewDecFromInt64(-123),
exp: NewDecFromInt64(0),
},
"1.234 - 1.234 = 0.000": {
x: NewDecWithExp(1234, -3),
y: NewDecWithExp(1234, -3),
exp: NewDecWithExp(0, -3),
},
"1.234 - 123 = -121.766": {
x: NewDecWithExp(1234, -3),
y: NewDecFromInt64(123),
exp: NewDecWithExp(-121766, -3),
},
"1.234 - -123 = 124.234": {
x: NewDecWithExp(1234, -3),
y: NewDecFromInt64(-123),
exp: NewDecWithExp(124234, -3),
},
"1.234 - -1.234 = 2.468": {
x: NewDecWithExp(1234, -3),
y: NewDecWithExp(-1234, -3),
exp: NewDecWithExp(2468, -3),
},
"-1.234 - -1.234 = 2.468": {
x: NewDecWithExp(-1234, -3),
y: NewDecWithExp(-1234, -3),
exp: NewDecWithExp(0, -3),
},
"1 - 0.999 = 0.001 - rounding after comma": {
x: NewDecFromInt64(1),
y: NewDecWithExp(999, -3),
exp: NewDecWithExp(1, -3),
},
"1e100000 - 1^-1 -> Err": {
x: NewDecWithExp(1, 100_000),
y: NewDecWithExp(1, -1),
expErr: ErrInvalidDec,
},
"1e100000 - 1^1-> Err": {
x: NewDecWithExp(1, 100_000),
y: NewDecWithExp(1, -1),
expErr: ErrInvalidDec,
},
"upper exp limit exceeded": {
x: NewDecWithExp(1, 100_001),
y: NewDecWithExp(1, 100_001),
expErr: ErrInvalidDec,
},
"lower exp limit exceeded": {
x: NewDecWithExp(1, -100_001),
y: NewDecWithExp(1, -100_001),
expErr: ErrInvalidDec,
},
"1e100000 - 1 = 999..9": {
x: NewDecWithExp(1, 100_000),
y: NewDecFromInt64(1),
exp: must(NewDecFromString(strings.Repeat("9", 100_000))),
},
"1e100000 - 0 = 1e100000": {
x: NewDecWithExp(1, 100_000),
y: NewDecFromInt64(0),
exp: must(NewDecFromString("1e100000")),
},
"1e100001 - 0 -> err": {
x: NewDecWithExp(1, 100_001),
y: NewDecFromInt64(0),
expErr: ErrInvalidDec,
},
"1e100000 - -1 -> 100..1": {
x: NewDecWithExp(1, 100_000),
y: must(NewDecFromString("-9e100000")),
expErr: ErrInvalidDec,
},
"1e-100000 - 0 = 1e-100000": {
x: NewDecWithExp(1, -100_000),
y: NewDecFromInt64(0),
exp: must(NewDecFromString("1e-100000")),
},
"1e-100001 - 0 -> err": {
x: NewDecWithExp(1, -100_001),
y: NewDecFromInt64(0),
expErr: ErrInvalidDec,
},
"1e-100000 - -1 -> 0.000..01": {
x: NewDecWithExp(1, -100_000),
y: NewDecFromInt64(-1),
exp: must(NewDecFromString("1." + strings.Repeat("0", 99999) + "1")),
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
got, gotErr := spec.x.Sub(spec.y)
if spec.expErr != nil {
require.ErrorIs(t, gotErr, spec.expErr)
return
}
require.NoError(t, gotErr)
assert.True(t, spec.exp.Equal(got), got.String())
})
}
}
func TestQuo(t *testing.T) {
specs := map[string]struct {
src string
x Dec
y Dec
exp Dec
expErr error
}{
"0 / 0 -> Err": {
x: NewDecFromInt64(0),
y: NewDecFromInt64(0),
expErr: ErrInvalidDec,
},
" 0 / 123 = 0": {
x: NewDecFromInt64(0),
y: NewDecFromInt64(123),
exp: NewDecFromInt64(0),
},
"123 / 0 = 0": {
x: NewDecFromInt64(123),
y: NewDecFromInt64(0),
expErr: ErrInvalidDec,
},
"-123 / 0 = 0": {
x: NewDecFromInt64(-123),
y: NewDecFromInt64(0),
expErr: ErrInvalidDec,
},
"123 / 123 = 1": {
x: NewDecFromInt64(123),
y: NewDecFromInt64(123),
exp: must(NewDecFromString("1.000000000000000000000000000000000")),
},
"-123 / 123 = -1": {
x: NewDecFromInt64(-123),
y: NewDecFromInt64(123),
exp: must(NewDecFromString("-1.000000000000000000000000000000000")),
},
"-123 / -123 = 1": {
x: NewDecFromInt64(-123),
y: NewDecFromInt64(-123),
exp: must(NewDecFromString("1.000000000000000000000000000000000")),
},
"1.234 / 1.234 = 1": {
x: NewDecWithExp(1234, -3),
y: NewDecWithExp(1234, -3),
exp: must(NewDecFromString("1.000000000000000000000000000000000")),
},
"-1.234 / 1234 = -1": {
x: NewDecWithExp(-1234, -3),
y: NewDecWithExp(1234, -3),
exp: must(NewDecFromString("-1.000000000000000000000000000000000")),
},
"1.234 / -123 = 1.0100": {
x: NewDecWithExp(1234, -3),
y: NewDecFromInt64(-123),
exp: must(NewDecFromString("-0.01003252032520325203252032520325203")),
},
"1.234 / -1.234 = -1": {
x: NewDecWithExp(1234, -3),
y: NewDecWithExp(-1234, -3),
exp: must(NewDecFromString("-1.000000000000000000000000000000000")),
},
"-1.234 / -1.234 = 1": {
x: NewDecWithExp(-1234, -3),
y: NewDecWithExp(-1234, -3),
exp: must(NewDecFromString("1.000000000000000000000000000000000")),
},
"3 / -9 = -0.3333...3 - round down": {
x: NewDecFromInt64(3),
y: NewDecFromInt64(-9),
exp: must(NewDecFromString("-0.3333333333333333333333333333333333")),
},
"4 / 9 = 0.4444...4 - round down": {
x: NewDecFromInt64(4),
y: NewDecFromInt64(9),
exp: must(NewDecFromString("0.4444444444444444444444444444444444")),
},
"5 / 9 = 0.5555...6 - round up": {
x: NewDecFromInt64(5),
y: NewDecFromInt64(9),
exp: must(NewDecFromString("0.5555555555555555555555555555555556")),
},
"6 / 9 = 0.6666...7 - round up": {
x: NewDecFromInt64(6),
y: NewDecFromInt64(9),
exp: must(NewDecFromString("0.6666666666666666666666666666666667")),
},
"7 / 9 = 0.7777...8 - round up": {
x: NewDecFromInt64(7),
y: NewDecFromInt64(9),
exp: must(NewDecFromString("0.7777777777777777777777777777777778")),
},
"8 / 9 = 0.8888...9 - round up": {
x: NewDecFromInt64(8),
y: NewDecFromInt64(9),
exp: must(NewDecFromString("0.8888888888888888888888888888888889")),
},
"9e-34 / 10 = 9e-35 - no rounding": {
x: NewDecWithExp(9, -34),
y: NewDecFromInt64(10),
exp: must(NewDecFromString("9e-35")),
},
"9e-35 / 10 = 9e-36 - no rounding": {
x: NewDecWithExp(9, -35),
y: NewDecFromInt64(10),
exp: must(NewDecFromString("9e-36")),
},
"high precision - min/0.1": {
x: NewDecWithExp(1, -100_000),
y: NewDecWithExp(1, -1),
exp: NewDecWithExp(1, -99_999),
},
"high precision - min/1": {
x: NewDecWithExp(1, -100_000),
y: NewDecWithExp(1, 0),
exp: NewDecWithExp(1, -100_000),
},
"high precision - min/10": {
x: NewDecWithExp(1, -100_000),
y: NewDecWithExp(1, 1),
expErr: ErrInvalidDec,
},
"high precision - <_min/0.1": {
x: NewDecWithExp(1, -100_001),
y: NewDecWithExp(1, -1),
exp: NewDecWithExp(1, -100_000),
},
"high precision - <_min/1": {
x: NewDecWithExp(1, -100_001),
y: NewDecWithExp(1, 0),
expErr: ErrInvalidDec,
},
"high precision - <_min/10": {
x: NewDecWithExp(1, -100_001),
y: NewDecWithExp(1, 1),
expErr: ErrInvalidDec,
},
"high precision - min/-0.1": {
x: NewDecWithExp(1, -100_000),
y: NewDecWithExp(-1, -1),
exp: NewDecWithExp(-1, -99_999),
},
"high precision - min/-1": {
x: NewDecWithExp(1, -100_000),
y: NewDecWithExp(-1, 0),
exp: NewDecWithExp(-1, -100_000),
},
"high precision - min/-10": {
x: NewDecWithExp(1, -100_000),
y: NewDecWithExp(-1, 1),
expErr: ErrInvalidDec,
},
"high precision - <_min/-0.1": {
x: NewDecWithExp(1, -100_001),
y: NewDecWithExp(-1, -1),
exp: NewDecWithExp(-1, -100_000),
},
"high precision - <_min/-1": {
x: NewDecWithExp(1, -100_001),
y: NewDecWithExp(-1, 0),
expErr: ErrInvalidDec,
},
"high precision - <_min/-10": {
x: NewDecWithExp(1, -100_001),
y: NewDecWithExp(-1, 1),
expErr: ErrInvalidDec,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
got, gotErr := spec.x.Quo(spec.y)
if spec.expErr != nil {
require.ErrorIs(t, gotErr, spec.expErr)
return
}
require.NoError(t, gotErr)
last35 := func(s string) string {
var x int
if len(s) < 36 {
x = 0
} else {
x = len(s) - 36
}
return fmt.Sprintf("%s(%d)", s[x:], len(s))
}
gotReduced, _ := got.Reduce()
assert.True(t, spec.exp.Equal(gotReduced), "exp %s, got: %s", last35(spec.exp.String()), last35(gotReduced.String()))
})
}
}
func TestQuoExact(t *testing.T) {
specs := map[string]struct {
src string
x Dec
y Dec
exp Dec
expErr error
}{
"0 / 0 -> Err": {
x: NewDecFromInt64(0),
y: NewDecFromInt64(0),
expErr: ErrInvalidDec,
},
" 0 / 123 = 0": {
x: NewDecFromInt64(0),
y: NewDecFromInt64(123),
exp: NewDecFromInt64(0),
},
"123 / 0 -> Err": {
x: NewDecFromInt64(123),
y: NewDecFromInt64(0),
expErr: ErrInvalidDec,
},
"-123 / 0 -> Err": {
x: NewDecFromInt64(-123),
y: NewDecFromInt64(0),
expErr: ErrInvalidDec,
},
"123 / 123 = 1": {
x: NewDecFromInt64(123),
y: NewDecFromInt64(123),
exp: must(NewDecFromString("1.000000000000000000000000000000000")),
},
"-123 / 123 = 1": {
x: NewDecFromInt64(-123),
y: NewDecFromInt64(123),
exp: must(NewDecFromString("-1.000000000000000000000000000000000")),
},
"-123 / -123 = 1": {
x: NewDecFromInt64(-123),
y: NewDecFromInt64(-123),
exp: must(NewDecFromString("1.000000000000000000000000000000000")),
},
"1.234 / 1.234 = 1": {
x: NewDecWithExp(1234, -3),
y: NewDecWithExp(1234, -3),
exp: must(NewDecFromString("1.000000000000000000000000000000000")),
},
"-1.234 / 1.234 = -1": {
x: NewDecWithExp(-1234, -3),
y: NewDecWithExp(1234, -3),
exp: must(NewDecFromString("-1.000000000000000000000000000000000")),
},
"1.234 / -123 -> Err": {
x: NewDecWithExp(1234, -3),
y: NewDecFromInt64(-123),
expErr: ErrUnexpectedRounding,
},
"1.234 / -1.234 = -1": {
x: NewDecWithExp(1234, -3),
y: NewDecWithExp(-1234, -3),
exp: must(NewDecFromString("-1.000000000000000000000000000000000")),
},
"-1.234 / -1.234 = 1": {
x: NewDecWithExp(-1234, -3),
y: NewDecWithExp(-1234, -3),
exp: must(NewDecFromString("1.000000000000000000000000000000000")),
},
"3 / -9 -> Err": {
x: NewDecFromInt64(3),
y: NewDecFromInt64(-9),
expErr: ErrUnexpectedRounding,
},
"4 / 9 -> Err": {
x: NewDecFromInt64(4),
y: NewDecFromInt64(9),
expErr: ErrUnexpectedRounding,
},
"5 / 9 -> Err": {
x: NewDecFromInt64(5),
y: NewDecFromInt64(9),
expErr: ErrUnexpectedRounding,
},
"6 / 9 -> Err": {
x: NewDecFromInt64(6),
y: NewDecFromInt64(9),
expErr: ErrUnexpectedRounding,
},
"7 / 9 -> Err": {
x: NewDecFromInt64(7),
y: NewDecFromInt64(9),
expErr: ErrUnexpectedRounding,
},
"8 / 9 -> Err": {
x: NewDecFromInt64(8),
y: NewDecFromInt64(9),
expErr: ErrUnexpectedRounding,
},
"9e-34 / 10 = 9e-35 - no rounding": {
x: NewDecWithExp(9, -34),
y: NewDecFromInt64(10),
exp: must(NewDecFromString("0.00000000000000000000000000000000009000000000000000000000000000000000")),
},
"9e-35 / 10 = 9e-36 - no rounding": {
x: NewDecWithExp(9, -35),
y: NewDecFromInt64(10),
exp: must(NewDecFromString("9e-36")),
},
"high precision - min/0.1": {
x: NewDecWithExp(1, -100_000),
y: NewDecWithExp(1, -1),
exp: NewDecWithExp(1, -99_999),
},
"high precision - min/1": {
x: NewDecWithExp(1, -100_000),
y: NewDecWithExp(1, 0),
exp: NewDecWithExp(1, -100_000),
},
"high precision - min/10": {
x: NewDecWithExp(1, -100_000),
y: NewDecWithExp(1, 1),
expErr: ErrInvalidDec,
},
"high precision - <_min/0.1": {
x: NewDecWithExp(1, -100_001),
y: NewDecWithExp(1, -1),
exp: NewDecWithExp(1, -100_000),
},
"high precision - <_min/1": {
x: NewDecWithExp(1, -100_001),
y: NewDecWithExp(1, 0),
expErr: ErrInvalidDec,
},
"high precision - <_min/10 -> Err": {
x: NewDecWithExp(1, -100_001),
y: NewDecWithExp(1, 1),
expErr: ErrInvalidDec,
},
"high precision - min/-0.1": {
x: NewDecWithExp(1, -100_000),
y: NewDecWithExp(-1, -1),
exp: NewDecWithExp(-1, -99_999),
},
"high precision - min/-1": {
x: NewDecWithExp(1, -100_000),
y: NewDecWithExp(-1, 0),
exp: NewDecWithExp(-1, -100_000),
},
"high precision - min/-10 -> Err": {
x: NewDecWithExp(1, -100_000),
y: NewDecWithExp(-1, 1),
expErr: ErrInvalidDec,
},
"high precision - <_min/-0.1": {
x: NewDecWithExp(1, -100_001),
y: NewDecWithExp(-1, -1),
exp: NewDecWithExp(-1, -100_000),
},
"high precision - <_min/-1": {
x: NewDecWithExp(1, -100_001),
y: NewDecWithExp(-1, 0),
expErr: ErrInvalidDec,
},
"high precision - <_min/-10": {
x: NewDecWithExp(1, -100_001),
y: NewDecWithExp(-1, 1),
expErr: ErrInvalidDec,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
got, gotErr := spec.x.QuoExact(spec.y)
if spec.expErr != nil {
require.ErrorIs(t, gotErr, spec.expErr)
return
}
require.NoError(t, gotErr)
assert.True(t, spec.exp.Equal(got))
})
}
}
func TestQuoInteger(t *testing.T) {
specs := map[string]struct {
src string
x Dec
y Dec
exp Dec
expErr error
}{
"0 / 0 -> Err": {
x: NewDecFromInt64(0),
y: NewDecFromInt64(0),
expErr: ErrInvalidDec,
},
" 0 / 123 = 0": {
x: NewDecFromInt64(0),
y: NewDecFromInt64(123),
exp: NewDecFromInt64(0),
},
"123 / 0 -> Err": {
x: NewDecFromInt64(123),
y: NewDecFromInt64(0),
expErr: ErrInvalidDec,
},
"-123 / -> Err": {
x: NewDecFromInt64(-123),
y: NewDecFromInt64(0),
expErr: ErrInvalidDec,
},
"123 / 123 = 1": {
x: NewDecFromInt64(123),
y: NewDecFromInt64(123),
exp: NewDecFromInt64(1),
},
"-123 / 123 = -1": {
x: NewDecFromInt64(-123),
y: NewDecFromInt64(123),
exp: NewDecFromInt64(-1),
},
"-123 / -123 = 1": {
x: NewDecFromInt64(-123),
y: NewDecFromInt64(-123),
exp: NewDecFromInt64(1),
},
"1.234 / 1.234": {
x: NewDecWithExp(1234, -3),
y: NewDecWithExp(1234, -3),
exp: NewDecFromInt64(1),
},
"-1.234 / 1234 = -121.766": {
x: NewDecWithExp(-1234, -3),
y: NewDecWithExp(1234, -3),
exp: NewDecFromInt64(-1),
},
"1.234 / -1.234 = 2.468": {
x: NewDecWithExp(1234, -3),
y: NewDecWithExp(-1234, -3),
exp: NewDecFromInt64(-1),
},
"-1.234 / -1.234 = 1": {
x: NewDecWithExp(-1234, -3),
y: NewDecWithExp(-1234, -3),
exp: NewDecFromInt64(1),
},
"3 / -9 = 0": {
x: NewDecFromInt64(3),
y: NewDecFromInt64(-9),
exp: must(NewDecFromString("0")),
},
"8 / 9 = 0": {
x: NewDecFromInt64(8),
y: NewDecFromInt64(9),
exp: must(NewDecFromString("0")),
},
"high precision - min/0.1": {
x: NewDecWithExp(1, -100_000),
y: NewDecWithExp(1, -1),
exp: NewDecFromInt64(0),
},
"high precision - <_min/-1 -> Err": {
x: NewDecWithExp(1, -100_001),
y: NewDecWithExp(-1, 0),
expErr: ErrInvalidDec,
},
"high precision - <_min/-10 -> Err": {
x: NewDecWithExp(1, -100_001),
y: NewDecWithExp(-1, 1),
expErr: ErrInvalidDec,
},
"1e000 / 1 -> Err": {
x: NewDecWithExp(1, 100_000),
y: NewDecFromInt64(1),
expErr: ErrInvalidDec,
},
"1e100000 - 1^1 -> Err": {
x: NewDecWithExp(1, 100_000),
y: NewDecWithExp(1, -1),
expErr: ErrInvalidDec,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
got, gotErr := spec.x.QuoInteger(spec.y)
if spec.expErr != nil {
require.ErrorIs(t, gotErr, spec.expErr)
return
}
require.NoError(t, gotErr)
assert.True(t, spec.exp.Equal(got))
})
}
}
func TestModulo(t *testing.T) {
specs := map[string]struct {
x Dec
y Dec
exp Dec
expErr error
}{
"0 / 123 = 0": {
x: NewDecFromInt64(0),
y: NewDecFromInt64(123),
exp: NewDecFromInt64(0),
},
"123 / 10 = 3": {
x: NewDecFromInt64(123),
y: NewDecFromInt64(10),
exp: NewDecFromInt64(3),
},
"123 / -10 = 3": {
x: NewDecFromInt64(123),
y: NewDecFromInt64(-10),
exp: NewDecFromInt64(3),
},
"-123 / 10 = -3": {
x: NewDecFromInt64(-123),
y: NewDecFromInt64(10),
exp: NewDecFromInt64(-3),
},
"1.234 / 1 = 0.234": {
x: NewDecWithExp(1234, -3),
y: NewDecFromInt64(1),
exp: NewDecWithExp(234, -3),
},
"1.234 / 0.1 = 0.034": {
x: NewDecWithExp(1234, -3),
y: NewDecWithExp(1, -1),
exp: NewDecWithExp(34, -3),
},
"1.234 / 1.1 = 0.134": {
x: NewDecWithExp(1234, -3),
y: NewDecWithExp(11, -1),
exp: NewDecWithExp(134, -3),
},
"10 / 0 -> Err": {
x: NewDecFromInt64(10),
y: NewDecFromInt64(0),
expErr: ErrInvalidDec,
},
"-1e0000 / 9e0000 = 1e0000": {
x: NewDecWithExp(-1, 100_000),
y: NewDecWithExp(9, 100_000),
exp: NewDecWithExp(-1, 100_000),
},
"1e0000 / 9e0000 = 1e0000": {
x: NewDecWithExp(1, 100_000),
y: NewDecWithExp(9, 100_000),
exp: NewDecWithExp(1, 100_000),
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
got, gotErr := spec.x.Modulo(spec.y)
if spec.expErr != nil {
require.ErrorIs(t, gotErr, spec.expErr)
return
}
require.NoError(t, gotErr)
assert.True(t, spec.exp.Equal(got))
})
}
}
func TestNumDecimalPlaces(t *testing.T) {
specs := map[string]struct {
src Dec
exp uint32
}{
"integer": {
src: NewDecFromInt64(123),
exp: 0,
},
"one decimal place": {
src: NewDecWithExp(1234, -1),
exp: 1,
},
"two decimal places": {
src: NewDecWithExp(12345, -2),
exp: 2,
},
"three decimal places": {
src: NewDecWithExp(123456, -3),
exp: 3,
},
"trailing zeros": {
src: NewDecWithExp(123400, -4),
exp: 4,
},
"zero value": {
src: NewDecFromInt64(0),
exp: 0,
},
"negative value": {
src: NewDecWithExp(-12345, -3),
exp: 3,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
got := spec.src.NumDecimalPlaces()
assert.Equal(t, spec.exp, got)
})
}
}
func TestCmp(t *testing.T) {
specs := map[string]struct {
x Dec
y Dec
exp int
}{
"0 == 0": {
x: NewDecFromInt64(0),
y: NewDecFromInt64(0),
exp: 0,
},
"0 < 123 = -1": {
x: NewDecFromInt64(0),
y: NewDecFromInt64(123),
exp: -1,
},
"123 > 0 = 1": {
x: NewDecFromInt64(123),
y: NewDecFromInt64(0),
exp: 1,
},
"-123 < 0 = -1": {
x: NewDecFromInt64(-123),
y: NewDecFromInt64(0),
exp: -1,
},
"123 == 123": {
x: NewDecFromInt64(123),
y: NewDecFromInt64(123),
exp: 0,
},
"-123 == -123": {
x: NewDecFromInt64(-123),
y: NewDecFromInt64(-123),
exp: 0,
},
"1.234 == 1.234": {
x: NewDecWithExp(1234, -3),
y: NewDecWithExp(1234, -3),
exp: 0,
},
"1.234 > 1.233": {
x: NewDecWithExp(1234, -3),
y: NewDecWithExp(1233, -3),
exp: 1,
},
"1.233 < 1.234": {
x: NewDecWithExp(1233, -3),
y: NewDecWithExp(1234, -3),
exp: -1,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
got := spec.x.Cmp(spec.y)
assert.Equal(t, spec.exp, got)
})
}
}
func TestReduce(t *testing.T) {
specs := map[string]struct {
src string
exp string
decPlaces int
}{
"positive value": {
src: "10",
exp: "10",
decPlaces: 1,
},
"negative value": {
src: "-10",
exp: "-10",
decPlaces: 1,
},
"positive decimal": {
src: "1.30000",
exp: "1.3",
decPlaces: 4,
},
"negative decimal": {
src: "-1.30000",
exp: "-1.3",
decPlaces: 4,
},
"zero decimal and decimal places": {
src: "0.00000",
exp: "0",
decPlaces: 0,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
src := must(NewDecFromString(spec.src))
got, gotZerosRemoved := src.Reduce()
assert.Equal(t, spec.decPlaces, gotZerosRemoved)
assert.Equal(t, spec.exp, got.String())
})
}
}
func TestMulExact(t *testing.T) {
specs := map[string]struct {
x Dec
y Dec
exp Dec
expErr error
}{
"200 * 200 = 40000": {
x: NewDecFromInt64(200),
y: NewDecFromInt64(200),
exp: NewDecFromInt64(40000),
},
"-200 * -200 = 40000": {
x: NewDecFromInt64(-200),
y: NewDecFromInt64(-200),
exp: NewDecFromInt64(40000),
},
"-100 * -100 = 10000": {
x: NewDecFromInt64(-100),
y: NewDecFromInt64(-100),
exp: NewDecFromInt64(10000),
},
"0 * 0 = 0": {
x: NewDecFromInt64(0),
y: NewDecFromInt64(0),
exp: NewDecFromInt64(0),
},
"1.1 * 1.1 = 1.21": {
x: NewDecWithExp(11, -1),
y: NewDecWithExp(11, -1),
exp: NewDecWithExp(121, -2),
},
"1.000 * 1.000 = 1.000000": {
x: NewDecWithExp(1000, -3),
y: NewDecWithExp(1000, -3),
exp: must(NewDecFromString("1.000000")),
},
"0.0000001 * 0.0000001 = 0": {
x: NewDecWithExp(0o0000001, -7),
y: NewDecWithExp(0o0000001, -7),
exp: NewDecWithExp(1, -14),
},
"0.12345678901234567890123456789012345 * 1": {
x: must(NewDecFromString("0.12345678901234567890123456789012345")),
y: NewDecWithExp(1, 0),
expErr: ErrUnexpectedRounding,
},
"0.12345678901234567890123456789012345 * 0": {
x: must(NewDecFromString("0.12345678901234567890123456789012345")),
y: NewDecFromInt64(0),
exp: NewDecFromInt64(0),
},
"0.12345678901234567890123456789012345 * 0.1": {
x: must(NewDecFromString("0.12345678901234567890123456789012345")),
y: NewDecWithExp(1, -1),
expErr: ErrUnexpectedRounding,
},
"1000001 * 1.000001 = 1000002.000001": {
x: NewDecFromInt64(1000001),
y: NewDecWithExp(1000001, -6),
exp: must(NewDecFromString("1000002.000001")),
},
"1000001 * 1000000 = 1000001000000 ": {
x: NewDecFromInt64(1000001),
y: NewDecFromInt64(1000000),
exp: NewDecFromInt64(1000001000000),
},
"1e0000 * 1e0000 -> Err": {
x: NewDecWithExp(1, 100_000),
y: NewDecWithExp(1, 100_000),
expErr: ErrInvalidDec,
},
"1e0000 * 1 = 1e0000": {
x: NewDecWithExp(1, 100_000),
y: NewDecWithExp(1, 0),
exp: NewDecWithExp(1, 100_000),
},
"1e100000 * 9 = 9e100000": {
x: NewDecWithExp(1, 100_000),
y: NewDecFromInt64(9),
exp: NewDecWithExp(9, 100_000),
},
"1e100000 * 10 = err": {
x: NewDecWithExp(1, 100_000),
y: NewDecWithExp(1, 1),
expErr: ErrInvalidDec,
},
"1e0000 * -1 = -1e0000": {
x: NewDecWithExp(1, 100_000),
y: NewDecWithExp(-1, 0),
exp: NewDecWithExp(-1, 100_000),
},
"1e100000 * -9 = 9e100000": {
x: NewDecWithExp(1, 100_000),
y: NewDecFromInt64(-9),
exp: NewDecWithExp(-9, 100_000),
},
"1e100000 * -10 = err": {
x: NewDecWithExp(1, 100_000),
y: NewDecWithExp(-1, 1),
expErr: ErrInvalidDec,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
got, gotErr := spec.x.MulExact(spec.y)
if spec.expErr != nil {
require.ErrorIs(t, gotErr, spec.expErr, gotErr)
return
}
require.NoError(t, gotErr)
assert.True(t, spec.exp.Equal(got), "exp: %s, got: %s", spec.exp.Text('E'), got.Text('E'))
})
}
}
func TestToBigInt(t *testing.T) {
i1 := "1000000000000000000000000000000000000123456789"
tcs := []struct {
intStr string
out string
isError error
}{
{i1, i1, nil},
{"1000000000000000000000000000000000000123456789.00000000", i1, nil},
{"123.456e6", "123456000", nil},
{"12345.6", "", ErrNonIntegral},
}
for idx, tc := range tcs {
t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) {
a, err := NewDecFromString(tc.intStr)
require.NoError(t, err)
b, err := a.BigInt()
if tc.isError == nil {
require.NoError(t, err, "test_%d", idx)
require.Equal(t, tc.out, b.String(), "test_%d", idx)
} else {
require.ErrorIs(t, err, tc.isError, "test_%d", idx)
}
})
}
}
func TestToSdkInt(t *testing.T) {
maxIntValue := "115792089237316195423570985008687907853269984665640564039457584007913129639935" // 2^256 -1
tcs := []struct {
src string
exp string
expErr bool
}{
{src: maxIntValue, exp: maxIntValue},
{src: "1000000000000000000000000000000000000123456789.00000001", exp: "1000000000000000000000000000000000000123456789"},
{src: "123.456e6", exp: "123456000"},
{src: "123.456e1", exp: "1234"},
{src: "123.456", exp: "123"},
{src: "123.956", exp: "123"},
{src: "-123.456", exp: "-123"},
{src: "-123.956", exp: "-123"},
{src: "-0.956", exp: "0"},
{src: "-0.9", exp: "0"},
{src: "1E-100000", exp: "0"},
{src: "115792089237316195423570985008687907853269984665640564039457584007913129639936", expErr: true}, // 2^256
{src: "1E100000", expErr: true},
}
for _, tc := range tcs {
t.Run(fmt.Sprint(tc.src), func(t *testing.T) {
a, err := NewDecFromString(tc.src)
require.NoError(t, err)
b, gotErr := a.SdkIntTrim()
if tc.expErr {
require.Error(t, gotErr, "value: %s", b.String())
return
}
require.NoError(t, gotErr)
require.Equal(t, tc.exp, b.String())
})
}
}
func TestInfDecString(t *testing.T) {
_, err := NewDecFromString("iNf")
require.Error(t, err)
require.ErrorIs(t, err, ErrInvalidDec)
}
func must[T any](r T, err error) T {
if err != nil {
panic(err)
}
return r
}
func TestMarshalUnmarshal(t *testing.T) {
specs := map[string]struct {
x Dec
exp string
expErr error
}{
"Zero value": {
x: NewDecFromInt64(0),
exp: "0",
},
"-0": {
x: NewDecFromInt64(-0),
exp: "0",
},
"1 decimal place": {
x: must(NewDecFromString("0.1")),
exp: "0.1",
},
"2 decimal places": {
x: must(NewDecFromString("0.01")),
exp: "0.01",
},
"3 decimal places": {
x: must(NewDecFromString("0.001")),
exp: "0.001",
},
"4 decimal places": {
x: must(NewDecFromString("0.0001")),
exp: "0.0001",
},
"5 decimal places": {
x: must(NewDecFromString("0.00001")),
exp: "0.00001",
},
"6 decimal places": {
x: must(NewDecFromString("0.000001")),
exp: "1E-6",
},
"7 decimal places": {
x: must(NewDecFromString("0.0000001")),
exp: "1E-7",
},
"1": {
x: must(NewDecFromString("1")),
exp: "1",
},
"12": {
x: must(NewDecFromString("12")),
exp: "12",
},
"123": {
x: must(NewDecFromString("123")),
exp: "123",
},
"1234": {
x: must(NewDecFromString("1234")),
exp: "1234",
},
"12345": {
x: must(NewDecFromString("12345")),
exp: "12345",
},
"123456": {
x: must(NewDecFromString("123456")),
exp: "123456",
},
"1234567": {
x: must(NewDecFromString("1234567")),
exp: "1.234567E+6",
},
"12345678": {
x: must(NewDecFromString("12345678")),
exp: "12.345678E+6",
},
"123456789": {
x: must(NewDecFromString("123456789")),
exp: "123.456789E+6",
},
"1234567890": {
x: must(NewDecFromString("1234567890")),
exp: "123.456789E+7",
},
"12345678900": {
x: must(NewDecFromString("12345678900")),
exp: "123.456789E+8",
},
"negative 1 with negative exponent": {
x: must(NewDecFromString("-1.000001")),
exp: "-1.000001",
},
"-1.0000001 - negative 1 with negative exponent": {
x: must(NewDecFromString("-1.0000001")),
exp: "-1.0000001",
},
"3 decimal places before the comma": {
x: must(NewDecFromString("100")),
exp: "100",
},
"4 decimal places before the comma": {
x: must(NewDecFromString("1000")),
exp: "1000",
},
"5 decimal places before the comma": {
x: must(NewDecFromString("10000")),
exp: "10000",
},
"6 decimal places before the comma": {
x: must(NewDecFromString("100000")),
exp: "100000",
},
"7 decimal places before the comma": {
x: must(NewDecFromString("1000000")),
exp: "1E+6",
},
"1e100000": {
x: NewDecWithExp(1, 100_000),
exp: "1E+100000",
},
"1.1e100000": {
x: must(NewDecFromString("1.1e100000")),
exp: "1.1E+100000",
},
"1e100001": {
x: NewDecWithExp(1, 100_001),
expErr: ErrInvalidDec,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
marshaled, gotErr := spec.x.Marshal()
if spec.expErr != nil {
require.ErrorIs(t, gotErr, spec.expErr)
return
}
require.NoError(t, gotErr)
assert.Equal(t, spec.exp, string(marshaled))
// and backwards
unmarshalledDec := new(Dec)
require.NoError(t, unmarshalledDec.Unmarshal(marshaled))
assert.Equal(t, spec.exp, unmarshalledDec.String())
assert.True(t, spec.x.Equal(*unmarshalledDec))
})
}
}